image-asset-manager
Version:
A comprehensive image asset management tool for frontend projects
297 lines • 10.8 kB
JavaScript
"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