UNPKG

image-asset-manager

Version:

A comprehensive image asset management tool for frontend projects

297 lines 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebSocketManagerImpl = void 0; const ws_1 = require("ws"); class WebSocketManagerImpl { constructor() { this.wss = null; this.clients = new Set(); this.serverData = null; } initialize(server) { this.wss = new ws_1.WebSocketServer({ server, path: "/ws", perMessageDeflate: false, }); this.setupEventHandlers(); console.log("📡 WebSocket server initialized"); } setupEventHandlers() { if (!this.wss) return; this.wss.on("connection", (ws, request) => { this.clients.add(ws); console.log(`📡 WebSocket client connected (${this.clients.size} total clients)`); // Send initial data to new client this.sendInitialData(ws); // Handle client messages ws.on("message", (message) => { try { const data = JSON.parse(message.toString()); this.handleClientMessage(ws, data); } catch (error) { console.error("📡 Failed to parse WebSocket message:", error); } }); // Handle client disconnect ws.on("close", (code, reason) => { this.clients.delete(ws); console.log(`📡 WebSocket client disconnected (${this.clients.size} remaining clients)`); }); // Handle client errors ws.on("error", (error) => { console.error("📡 WebSocket client error:", error); this.clients.delete(ws); }); // Send ping to keep connection alive const pingInterval = setInterval(() => { if (ws.readyState === ws_1.WebSocket.OPEN) { ws.ping(); } else { clearInterval(pingInterval); } }, 30000); // Ping every 30 seconds ws.on("pong", () => { // Client responded to ping, connection is alive }); }); this.wss.on("error", (error) => { console.error("📡 WebSocket server error:", error); }); } sendInitialData(ws) { if (!this.serverData) return; const initialMessage = { type: "initial-data", data: { stats: this.serverData.stats, imageCount: this.serverData.images.length, duplicateCount: this.serverData.duplicates.length, unusedCount: this.serverData.usage.unusedFiles.length, categories: this.getCategorySummary(), formats: this.getFormatSummary(), }, timestamp: Date.now(), }; this.sendToClient(ws, initialMessage); } handleClientMessage(ws, message) { switch (message.type) { case "ping": this.sendToClient(ws, { type: "pong", data: { timestamp: Date.now() }, timestamp: Date.now(), }); break; case "subscribe": // Client wants to subscribe to specific events const events = message.data?.events || []; console.log(`📡 Client subscribed to events: ${events.join(", ")}`); // Store subscription preferences (could be extended) break; case "request-data": // Client requests specific data this.handleDataRequest(ws, message.data); break; default: console.warn(`📡 Unknown message type: ${message.type}`); } } handleDataRequest(ws, requestData) { if (!this.serverData) return; switch (requestData?.type) { case "stats": this.sendToClient(ws, { type: "stats-data", data: this.serverData.stats, timestamp: Date.now(), }); break; case "images": const { page = 1, limit = 20, filters = {} } = requestData; const paginatedImages = this.getPaginatedImages(page, limit, filters); this.sendToClient(ws, { type: "images-data", data: paginatedImages, timestamp: Date.now(), }); break; case "duplicates": this.sendToClient(ws, { type: "duplicates-data", data: this.serverData.duplicates, timestamp: Date.now(), }); break; } } getPaginatedImages(page, limit, filters) { if (!this.serverData) return { images: [], pagination: {} }; let filteredImages = [...this.serverData.images]; // Apply filters if (filters.category && filters.category !== "all") { filteredImages = filteredImages.filter((img) => img.category === filters.category); } if (filters.type && filters.type !== "all") { filteredImages = filteredImages.filter((img) => img.extension === filters.type); } if (filters.unused === true) { const unusedIds = new Set(this.serverData.usage.unusedFiles.map((f) => f.id)); filteredImages = filteredImages.filter((img) => unusedIds.has(img.id)); } // Pagination const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; const paginatedImages = filteredImages.slice(startIndex, endIndex); return { images: paginatedImages, pagination: { page, limit, total: filteredImages.length, totalPages: Math.ceil(filteredImages.length / limit), }, }; } getCategorySummary() { if (!this.serverData) return {}; const categories = {}; this.serverData.images.forEach((img) => { categories[img.category] = (categories[img.category] || 0) + 1; }); return categories; } getFormatSummary() { if (!this.serverData) return {}; const formats = {}; this.serverData.images.forEach((img) => { const ext = img.extension.toLowerCase(); formats[ext] = (formats[ext] || 0) + 1; }); return formats; } broadcast(event, data) { if (!this.wss || this.clients.size === 0) return; const message = { type: event, data, timestamp: Date.now(), }; const messageStr = JSON.stringify(message); let successCount = 0; let failureCount = 0; this.clients.forEach((client) => { if (client.readyState === ws_1.WebSocket.OPEN) { try { client.send(messageStr); successCount++; } catch (error) { console.error("📡 Failed to send WebSocket message:", error); this.clients.delete(client); failureCount++; } } else { // Remove closed connections this.clients.delete(client); failureCount++; } }); if (failureCount > 0) { console.log(`📡 Broadcast complete: ${successCount} sent, ${failureCount} failed/removed`); } } handleFileChange(event) { console.log(`📡 Broadcasting file change: ${event.type} - ${event.path}`); // Broadcast the file change event this.broadcast("file-change", { type: event.type, path: event.path, file: event.file ? { id: event.file.id, name: event.file.name, relativePath: event.file.relativePath, extension: event.file.extension, size: event.file.size, category: event.file.category, dimensions: event.file.dimensions, } : null, }); // If we have updated server data, broadcast stats update if (this.serverData) { this.broadcast("stats-update", { imageCount: this.serverData.images.length, totalSize: this.serverData.stats.totalSize, categories: this.getCategorySummary(), formats: this.getFormatSummary(), }); } } updateServerData(data) { this.serverData = data; // Broadcast data update to all clients this.broadcast("data-updated", { stats: data.stats, imageCount: data.images.length, duplicateCount: data.duplicates.length, unusedCount: data.usage.unusedFiles.length, categories: this.getCategorySummary(), formats: this.getFormatSummary(), }); } sendToClient(ws, message) { if (ws.readyState === ws_1.WebSocket.OPEN) { try { ws.send(JSON.stringify(message)); } catch (error) { console.error("📡 Failed to send message to client:", error); this.clients.delete(ws); } } } close() { if (this.wss) { // Close all client connections this.clients.forEach((client) => { if (client.readyState === ws_1.WebSocket.OPEN) { client.close(1000, "Server shutting down"); } }); // Close the WebSocket server this.wss.close(() => { console.log("📡 WebSocket server closed"); }); this.wss = null; this.clients.clear(); } } // Utility methods getClientCount() { return this.clients.size; } isRunning() { return this.wss !== null; } // Send targeted messages to specific clients (could be extended for user sessions) sendToSpecificClient(clientId, message) { // This could be implemented if we need to track client IDs // For now, we broadcast to all clients this.broadcast(message.type, message.data); } } exports.WebSocketManagerImpl = WebSocketManagerImpl; //# sourceMappingURL=WebSocketManager.js.map