import { SocketClient } from "./SocketClient.js"; import { SubscriptionRegistry } from "./SubscriptionRegistry.js"; export class SocketManager { /** * @param {import("socket.io").Server} io * @param {object} [options] * @param {SubscriptionRegistry} [options.subscriptionRegistry] */ constructor(io, options = {}) { this.io = io; this.subscriptionRegistry = options.subscriptionRegistry ?? new SubscriptionRegistry(); this.clients = new Map(); /** @type {((socket: import("socket.io").Socket, client: SocketClient) => void) | null} */ this.registerSocketApi = null; this.setup(); } setup() { this.io.on("connection", (socket) => this.handleConnection(socket)); } handleConnection(socket) { const client = new SocketClient(socket); client.onDisconnect = (c) => { this.subscriptionRegistry.removeSocket(c.id); this.clients.delete(c.id); }; client.setupListeners(); if (this.registerSocketApi) { this.registerSocketApi(socket, client); } this.clients.set(socket.id, client); } /** * Broadcast { type, payload } to all clients (legacy / non-subscription paths). */ broadcast(data) { if (data && data.type !== undefined) { this.io.emit(data.type, data.payload); } else { this.io.emit("message", data); } } emitToSubscribers(channelKey, event, payload) { const ids = this.subscriptionRegistry.getSubscriberIds(channelKey); for (const socketId of ids) { this.io.to(socketId).emit(event, payload); } } emitPatchUpdated(patch) { if (!patch?.id) return; this.emitToSubscribers(`patch:${patch.id}`, "patchUpdated", { patch }); } emitWaterTankUpdated(payload) { this.emitToSubscribers("waterTank", "waterTankUpdated", payload); } emitPowerUpdated(payload) { this.emitToSubscribers("power", "powerUpdated", payload); } emitWateringSessionsUpdated(payload) { this.emitToSubscribers( "wateringSessions", "wateringSessionsUpdated", payload, ); } emitWaterSchedulesUpdated(payload) { this.emitToSubscribers( "waterSchedules", "waterSchedulesUpdated", payload, ); } getClient(id) { return this.clients.get(id); } }