Skip to content

Instantly share code, notes, and snippets.

@ivanmem
Last active March 24, 2025 21:49
Show Gist options
  • Save ivanmem/c144aa6135871366a3b61a3931efd2fa to your computer and use it in GitHub Desktop.
Save ivanmem/c144aa6135871366a3b61a3931efd2fa to your computer and use it in GitHub Desktop.
/* Основной класс логгера для управления выводом сообщений */
class Logger {
/**
* @param {boolean} enabled - Включено ли логирование
* @param {string} prefix - Префикс для сообщений
*/
constructor(enabled, prefix = "") {
this.enabled = enabled;
this.prefix = prefix;
}
/* Добавляет префикс к первому строковому аргументу */
createMessage(messages) {
if (!this.prefix) return messages;
return messages.map((msg, idx) =>
idx === 0 && typeof msg === "string" ? `[${this.prefix}] ${msg}` : msg
);
}
debug(...args) {
this.enabled && console.debug(...this.createMessage(args));
}
log(...args) {
this.enabled && console.log(...this.createMessage(args));
}
warn(...args) {
this.enabled && console.warn(...this.createMessage(args));
}
error(...args) {
this.enabled && console.error(...this.createMessage(args));
}
}
/* Менеджер длинных опросов (Longpoll) для обработки запросов */
class LongpollManager {
/**
* @param {Function} publishCallback - Функция для публикации результатов
* @param {Logger} logger - Экземпляр логгера
*/
constructor(publishCallback, logger) {
this._queues = new Map(); // Очереди запросов по ID
this._processing = new Map(); // Текущие обрабатываемые URL
this._retryCount = new Map(); // Счетчики повторных попыток
this._publish = publishCallback;
this._logger = logger;
}
/* Добавление нового запроса в очередь */
add(queueId, params) {
const existing = this._queues.get(queueId);
if (!existing || existing.ts < params.ts) {
this._queues.set(queueId, { ...params });
}
this._startProcessing();
}
/* Основной метод обработки URL */
async _processUrl(url) {
try {
const response = await this._request(url);
// Обработка успешного ответа
this._handleSuccessResponse(url, response);
} catch (error) {
// Обработка ошибок и повторные попытки
this._handleError(url, error);
} finally {
this._processing.delete(url);
this._startProcessing(); // Запускаем следующий цикл
}
}
/* Формирование URL для групповых запросов */
static createUrl(requests, logger) {
const baseUrl = requests[0].url;
const userId = requests[0].id;
let keys = requests[0].key;
let timestamps = `${requests[0].ts}`;
// Проверка согласованности параметров
for (let i = 1; i < requests.length; i++) {
if (requests[i].id !== userId) {
logger.error(`User ID mismatch`);
throw new Error("Invalid user ID");
}
// ... аналогичные проверки для URL
}
return `${baseUrl}?act=a_check&key=${keys}&id=${userId}&ts=${timestamps}&wait=25`;
}
}
/* Менеджер портов для коммуникации с клиентами */
class PortManager {
constructor(logger, disconnectCallback) {
this._ports = new Map(); // clientId -> порт
this._keepAliveTimer = null; // Таймер проверки активности
this._logger = logger;
this._onDisconnect = disconnectCallback;
}
/* Добавление нового порта */
add(clientId, port) {
port.onmessage = this._handleMessage.bind(this);
this._ports.set(clientId, port);
this._startKeepAliveCheck();
}
/* Проверка активности портов через ping/pong */
_startKeepAliveCheck() {
if (!this._keepAliveTimer && this._ports.size) {
this._keepAliveTimer = setInterval(() => {
this._ports.forEach((port, clientId) => {
const pingTimeout = setTimeout(() => {
this.remove(clientId); // Удаляем неответившие порты
}, 1000);
port.postMessage({ type: "ping" });
port.once("message", (msg) => {
if (msg.type === "pong") clearTimeout(pingTimeout);
});
});
}, 10000);
}
}
}
/* Менеджер учетных данных для аутентификации */
class CredentialsManager {
constructor(portManager, queuesManager, logger) {
this._portManager = portManager;
this._queuesManager = queuesManager;
this._activeRequests = new Set();
this._roundRobinIndex = new Map(); // Для ротации клиентов
}
/* Запрос учетных данных через Round Robin */
_processCredentials() {
this._activeRequests.forEach(queueId => {
const clients = this._queuesManager.getClients(queueId);
const lastUsed = this._roundRobinIndex.get(queueId) || 0;
const nextClient = clients[(lastUsed + 1) % clients.length];
if (nextClient) {
const port = this._portManager.getPort(nextClient);
port.postMessage({
type: "request-credentials",
payload: { queueId }
});
}
});
}
}
// Инициализация Shared Worker
const logger = new Logger(DEBUG_MODE, "Worker");
const portManager = new PortManager(logger, handleDisconnect);
const longpollManager = new LongpollManager(handleLongpollResponse, logger);
onconnect = ({ ports }) => {
const port = ports[0];
port.onmessage = ({ data }) => {
switch (data.type) {
case "connect":
portManager.add(data.clientId, port);
break;
case "subscribe":
longpollManager.add(data.queueId, data.params);
break;
// ... обработка других событий
}
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment