Instantly share code, notes, and snippets.
Last active
March 24, 2025 21:49
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save ivanmem/c144aa6135871366a3b61a3931efd2fa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Основной класс логгера для управления выводом сообщений */ | |
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