UNPKG

synapse-storage

Version:

Набор инструментов для управления состоянием и апи-запросами

140 lines (135 loc) 5.19 kB
import { loggerConsole } from "../../../_utils/logger-console.util.js"; class SyncBroadcastChannel { channel; tabId; messageHandlers; syncHandler; syncTimeoutMs = 1000; closed = false; pendingSyncRequests; constructor(channelName){ this.channel = new BroadcastChannel(channelName); this.tabId = crypto.randomUUID(); this.messageHandlers = new Set(); this.pendingSyncRequests = new Map(); this.channel.onmessage = this.handleMessage.bind(this); this.channel.onmessageerror = this.handleError.bind(this); } error(...args) { loggerConsole.error(`[SyncBroadcastChannel][${this.tabId}]`, ...args); } async handleMessage(event) { const message = event.data; // Игнорируем собственные сообщения if (message.senderId === this.tabId) { return; } // Обработка запроса на синхронизацию if (message.type === 'SYNC_REQUEST') { if (this.syncHandler) { try { const state = await this.syncHandler(); this.postMessage('SYNC_RESPONSE', state, message.senderId); } catch (error) { this.error('Error handling sync request:', error); } } return; } // Обработка ответа на запрос синхронизации if (message.type === 'SYNC_RESPONSE') { // Фильтруем по targetId — принимаем только ответы, адресованные этой вкладке if (message.targetId && message.targetId !== this.tabId) { return; } const request = this.pendingSyncRequests.get(this.tabId); if (request) { clearTimeout(request.timeout); this.pendingSyncRequests.delete(this.tabId); //@ts-ignore request.resolve(message.payload); } return; } // Уведомляем всех подписчиков о сообщении for (const handler of this.messageHandlers){ try { await handler(message); } catch (error) { this.error('Error in message handler:', error); } } } handleError(event) { this.error('Channel error:', event); } postMessage(type, payload, targetId) { const message = { type, payload, senderId: this.tabId, timestamp: Date.now() }; if (targetId) { message.targetId = targetId; } this.channel.postMessage(message); } /** * Подписка на сообщения канала */ subscribe(handler) { this.messageHandlers.add(handler); return ()=>this.messageHandlers.delete(handler); } /** * Установка обработчика запросов на синхронизацию */ setSyncHandler(handler) { this.syncHandler = handler; } /** * Отправка сообщения всем подписчикам */ broadcast(type, payload) { //@ts-ignore this.postMessage(type, payload); } /** Закрыт ли канал (dev double-mount / teardown). Потребители могут пропустить initial-sync. */ get isClosed() { return this.closed; } /** Запрос синхронизации данных с других вкладок. */ async requestSync() { // Канал уже закрыт (dev-teardown между mount'ами) — «нет ответа», без ошибки. if (this.closed) return null; return new Promise((resolve, reject)=>{ const timeout = setTimeout(()=>{ this.pendingSyncRequests.delete(this.tabId); resolve(null); }, this.syncTimeoutMs); this.pendingSyncRequests.set(this.tabId, { resolve, reject, timeout }); this.postMessage('SYNC_REQUEST', { type: 'sync' }); }); } /** * Закрытие канала */ close() { this.closed = true; // Pending initial-sync резолвим как «нет ответа» (как таймаут, resolve(null)) — закрытие канала // не ошибка (типичный dev double-mount в StrictMode/Fast Refresh), не шумим Error-стеком. for (const [, request] of this.pendingSyncRequests){ clearTimeout(request.timeout); request.resolve(null); } this.pendingSyncRequests.clear(); // Очищаем обработчики this.messageHandlers.clear(); this.syncHandler = undefined; // Закрываем канал this.channel.close(); } } export { SyncBroadcastChannel }; //# sourceMappingURL=broadcast.util.js.map