@superdangerous/app-framework 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +652 -0
- package/dist/api/logsRouter.d.ts +20 -0
- package/dist/api/logsRouter.d.ts.map +1 -0
- package/dist/api/logsRouter.js +515 -0
- package/dist/api/logsRouter.js.map +1 -0
- package/dist/cli/dev-server.d.ts +7 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +640 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/StandardServer.d.ts +129 -0
- package/dist/core/StandardServer.d.ts.map +1 -0
- package/dist/core/StandardServer.js +453 -0
- package/dist/core/StandardServer.js.map +1 -0
- package/dist/core/apiResponse.d.ts +69 -0
- package/dist/core/apiResponse.d.ts.map +1 -0
- package/dist/core/apiResponse.js +127 -0
- package/dist/core/apiResponse.js.map +1 -0
- package/dist/core/healthCheck.d.ts +160 -0
- package/dist/core/healthCheck.d.ts.map +1 -0
- package/dist/core/healthCheck.js +398 -0
- package/dist/core/healthCheck.js.map +1 -0
- package/dist/core/index.d.ts +40 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +40 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +117 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +826 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/portUtils.d.ts +71 -0
- package/dist/core/portUtils.d.ts.map +1 -0
- package/dist/core/portUtils.js +240 -0
- package/dist/core/portUtils.js.map +1 -0
- package/dist/core/storageService.d.ts +119 -0
- package/dist/core/storageService.d.ts.map +1 -0
- package/dist/core/storageService.js +405 -0
- package/dist/core/storageService.js.map +1 -0
- package/dist/desktop/bundler.d.ts +40 -0
- package/dist/desktop/bundler.d.ts.map +1 -0
- package/dist/desktop/bundler.js +176 -0
- package/dist/desktop/bundler.js.map +1 -0
- package/dist/desktop/index.d.ts +25 -0
- package/dist/desktop/index.d.ts.map +1 -0
- package/dist/desktop/index.js +15 -0
- package/dist/desktop/index.js.map +1 -0
- package/dist/desktop/native-modules.d.ts +66 -0
- package/dist/desktop/native-modules.d.ts.map +1 -0
- package/dist/desktop/native-modules.js +200 -0
- package/dist/desktop/native-modules.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/LogCategories.d.ts +87 -0
- package/dist/logging/LogCategories.d.ts.map +1 -0
- package/dist/logging/LogCategories.js +205 -0
- package/dist/logging/LogCategories.js.map +1 -0
- package/dist/middleware/aiErrorHandler.d.ts +31 -0
- package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
- package/dist/middleware/aiErrorHandler.js +181 -0
- package/dist/middleware/aiErrorHandler.js.map +1 -0
- package/dist/middleware/auth.d.ts +101 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +230 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +56 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +123 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/errorHandler.d.ts +13 -0
- package/dist/middleware/errorHandler.d.ts.map +1 -0
- package/dist/middleware/errorHandler.js +85 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/fileUpload.d.ts +62 -0
- package/dist/middleware/fileUpload.d.ts.map +1 -0
- package/dist/middleware/fileUpload.js +175 -0
- package/dist/middleware/fileUpload.js.map +1 -0
- package/dist/middleware/health.d.ts +48 -0
- package/dist/middleware/health.d.ts.map +1 -0
- package/dist/middleware/health.js +143 -0
- package/dist/middleware/health.js.map +1 -0
- package/dist/middleware/index.d.ts +20 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +18 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/openapi.d.ts +64 -0
- package/dist/middleware/openapi.d.ts.map +1 -0
- package/dist/middleware/openapi.js +258 -0
- package/dist/middleware/openapi.js.map +1 -0
- package/dist/middleware/requestLogging.d.ts +22 -0
- package/dist/middleware/requestLogging.d.ts.map +1 -0
- package/dist/middleware/requestLogging.js +61 -0
- package/dist/middleware/requestLogging.js.map +1 -0
- package/dist/middleware/session.d.ts +84 -0
- package/dist/middleware/session.d.ts.map +1 -0
- package/dist/middleware/session.js +189 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/validation.d.ts +1337 -0
- package/dist/middleware/validation.d.ts.map +1 -0
- package/dist/middleware/validation.js +483 -0
- package/dist/middleware/validation.js.map +1 -0
- package/dist/services/aiService.d.ts +180 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +547 -0
- package/dist/services/aiService.js.map +1 -0
- package/dist/services/conversationStorage.d.ts +38 -0
- package/dist/services/conversationStorage.d.ts.map +1 -0
- package/dist/services/conversationStorage.js +158 -0
- package/dist/services/conversationStorage.js.map +1 -0
- package/dist/services/crossPlatformBuffer.d.ts +84 -0
- package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
- package/dist/services/crossPlatformBuffer.js +246 -0
- package/dist/services/crossPlatformBuffer.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/networkService.d.ts +81 -0
- package/dist/services/networkService.d.ts.map +1 -0
- package/dist/services/networkService.js +268 -0
- package/dist/services/networkService.js.map +1 -0
- package/dist/services/queueService.d.ts +112 -0
- package/dist/services/queueService.d.ts.map +1 -0
- package/dist/services/queueService.js +338 -0
- package/dist/services/queueService.js.map +1 -0
- package/dist/services/settingsService.d.ts +135 -0
- package/dist/services/settingsService.d.ts.map +1 -0
- package/dist/services/settingsService.js +425 -0
- package/dist/services/settingsService.js.map +1 -0
- package/dist/services/systemMonitor.d.ts +208 -0
- package/dist/services/systemMonitor.d.ts.map +1 -0
- package/dist/services/systemMonitor.js +693 -0
- package/dist/services/systemMonitor.js.map +1 -0
- package/dist/services/updateService.d.ts +78 -0
- package/dist/services/updateService.d.ts.map +1 -0
- package/dist/services/updateService.js +252 -0
- package/dist/services/updateService.js.map +1 -0
- package/dist/services/websocketEvents.d.ts +372 -0
- package/dist/services/websocketEvents.d.ts.map +1 -0
- package/dist/services/websocketEvents.js +338 -0
- package/dist/services/websocketEvents.js.map +1 -0
- package/dist/services/websocketServer.d.ts +80 -0
- package/dist/services/websocketServer.d.ts.map +1 -0
- package/dist/services/websocketServer.js +299 -0
- package/dist/services/websocketServer.js.map +1 -0
- package/dist/settings/SettingsSchema.d.ts +151 -0
- package/dist/settings/SettingsSchema.d.ts.map +1 -0
- package/dist/settings/SettingsSchema.js +424 -0
- package/dist/settings/SettingsSchema.js.map +1 -0
- package/dist/testing/TestServer.d.ts +69 -0
- package/dist/testing/TestServer.d.ts.map +1 -0
- package/dist/testing/TestServer.js +250 -0
- package/dist/testing/TestServer.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/appPaths.d.ts +74 -0
- package/dist/utils/appPaths.d.ts.map +1 -0
- package/dist/utils/appPaths.js +162 -0
- package/dist/utils/appPaths.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +50 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +114 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/standardConfig.d.ts +61 -0
- package/dist/utils/standardConfig.d.ts.map +1 -0
- package/dist/utils/standardConfig.js +109 -0
- package/dist/utils/standardConfig.js.map +1 -0
- package/dist/utils/startupBanner.d.ts +34 -0
- package/dist/utils/startupBanner.d.ts.map +1 -0
- package/dist/utils/startupBanner.js +169 -0
- package/dist/utils/startupBanner.js.map +1 -0
- package/dist/utils/startupLogger.d.ts +45 -0
- package/dist/utils/startupLogger.d.ts.map +1 -0
- package/dist/utils/startupLogger.js +200 -0
- package/dist/utils/startupLogger.js.map +1 -0
- package/package.json +151 -0
- package/src/api/logsRouter.ts +600 -0
- package/src/cli/dev-server.ts +803 -0
- package/src/cli/index.ts +31 -0
- package/src/core/StandardServer.ts +587 -0
- package/src/core/apiResponse.ts +202 -0
- package/src/core/healthCheck.ts +565 -0
- package/src/core/index.ts +80 -0
- package/src/core/logger.ts +1092 -0
- package/src/core/portUtils.ts +319 -0
- package/src/core/storageService.ts +595 -0
- package/src/desktop/bundler.ts +271 -0
- package/src/desktop/index.ts +18 -0
- package/src/desktop/native-modules.ts +289 -0
- package/src/index.ts +142 -0
- package/src/logging/LogCategories.ts +302 -0
- package/src/middleware/aiErrorHandler.ts +278 -0
- package/src/middleware/auth.ts +329 -0
- package/src/middleware/cors.ts +187 -0
- package/src/middleware/errorHandler.ts +103 -0
- package/src/middleware/fileUpload.ts +252 -0
- package/src/middleware/health.ts +206 -0
- package/src/middleware/index.ts +71 -0
- package/src/middleware/openapi.ts +305 -0
- package/src/middleware/requestLogging.ts +92 -0
- package/src/middleware/session.ts +238 -0
- package/src/middleware/validation.ts +603 -0
- package/src/services/aiService.ts +789 -0
- package/src/services/conversationStorage.ts +232 -0
- package/src/services/crossPlatformBuffer.ts +341 -0
- package/src/services/index.ts +47 -0
- package/src/services/networkService.ts +351 -0
- package/src/services/queueService.ts +446 -0
- package/src/services/settingsService.ts +549 -0
- package/src/services/systemMonitor.ts +936 -0
- package/src/services/updateService.ts +334 -0
- package/src/services/websocketEvents.ts +409 -0
- package/src/services/websocketServer.ts +394 -0
- package/src/settings/SettingsSchema.ts +664 -0
- package/src/testing/TestServer.ts +312 -0
- package/src/types/index.ts +154 -0
- package/src/utils/appPaths.ts +196 -0
- package/src/utils/fs-utils.ts +130 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/standardConfig.ts +178 -0
- package/src/utils/startupBanner.ts +287 -0
- package/src/utils/startupLogger.ts +268 -0
- package/ui/dist/index.d.mts +1221 -0
- package/ui/dist/index.d.ts +1221 -0
- package/ui/dist/index.js +73 -0
- package/ui/dist/index.js.map +1 -0
- package/ui/dist/index.mjs +73 -0
- package/ui/dist/index.mjs.map +1 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Server Implementation
|
|
3
|
+
* Provides real-time updates for simulator data and events
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Server, Socket } from "socket.io";
|
|
7
|
+
import { Server as HTTPServer } from "http";
|
|
8
|
+
import { createLogger } from "../core/index.js";
|
|
9
|
+
import {
|
|
10
|
+
WebSocketMessage,
|
|
11
|
+
SimulatorUpdateMessage,
|
|
12
|
+
TemplateUpdateMessage,
|
|
13
|
+
DataUpdateMessage,
|
|
14
|
+
Simulator,
|
|
15
|
+
Template,
|
|
16
|
+
} from "../types/index.js";
|
|
17
|
+
|
|
18
|
+
let logger: any; // Will be initialized when needed
|
|
19
|
+
|
|
20
|
+
interface ClientInfo {
|
|
21
|
+
id: string;
|
|
22
|
+
connectedAt: Date;
|
|
23
|
+
subscriptions: Set<string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface BroadcastData {
|
|
27
|
+
simulatorId?: string;
|
|
28
|
+
templateId?: string;
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class WebSocketServer {
|
|
33
|
+
private io: Server | null = null;
|
|
34
|
+
private httpServer: HTTPServer;
|
|
35
|
+
private clients: Map<string, ClientInfo>;
|
|
36
|
+
private simulatorSubscriptions: Map<string, Set<string>>; // simulatorId -> Set of socket IDs
|
|
37
|
+
private broadcastHook?: (event: string, data: any) => void;
|
|
38
|
+
|
|
39
|
+
constructor(httpServer: HTTPServer) {
|
|
40
|
+
// Initialize logger if not already done
|
|
41
|
+
if (!logger) {
|
|
42
|
+
logger = createLogger("WebSocket");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.httpServer = httpServer;
|
|
46
|
+
this.clients = new Map();
|
|
47
|
+
this.simulatorSubscriptions = new Map();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
initialize(opts?: {
|
|
51
|
+
broadcastHook?: (event: string, data: any) => void;
|
|
52
|
+
}): void {
|
|
53
|
+
if (this.io) {
|
|
54
|
+
if (opts?.broadcastHook) {
|
|
55
|
+
this.broadcastHook = opts.broadcastHook;
|
|
56
|
+
logger.debug("WebSocket broadcast hook updated");
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.io = new Server(this.httpServer, {
|
|
62
|
+
cors: {
|
|
63
|
+
origin: "*", // Allow all origins for local development
|
|
64
|
+
methods: ["GET", "POST"],
|
|
65
|
+
},
|
|
66
|
+
transports: ["websocket", "polling"],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (opts?.broadcastHook) {
|
|
70
|
+
this.broadcastHook = opts.broadcastHook;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.setupEventHandlers();
|
|
74
|
+
logger.info("WebSocket server initialized");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the underlying Socket.IO server instance
|
|
79
|
+
*/
|
|
80
|
+
getIO(): Server | null {
|
|
81
|
+
return this.io;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private setupEventHandlers(): void {
|
|
85
|
+
if (!this.io) return;
|
|
86
|
+
|
|
87
|
+
this.io.on("connection", (socket: Socket) => {
|
|
88
|
+
// Only log in debug mode to avoid spam
|
|
89
|
+
if (process.env.LOG_LEVEL?.toLowerCase() === "debug") {
|
|
90
|
+
logger.debug(`Client connected: ${socket.id}`);
|
|
91
|
+
}
|
|
92
|
+
this.clients.set(socket.id, {
|
|
93
|
+
id: socket.id,
|
|
94
|
+
connectedAt: new Date(),
|
|
95
|
+
subscriptions: new Set(),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Send initial connection confirmation
|
|
99
|
+
socket.emit("connected", {
|
|
100
|
+
id: socket.id,
|
|
101
|
+
timestamp: Date.now(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Handle simulator subscriptions
|
|
105
|
+
socket.on("subscribe:simulator", (simulatorId: string) => {
|
|
106
|
+
this.subscribeToSimulator(socket, simulatorId);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
socket.on("unsubscribe:simulator", (simulatorId: string) => {
|
|
110
|
+
this.unsubscribeFromSimulator(socket, simulatorId);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Handle disconnection
|
|
114
|
+
socket.on("disconnect", () => {
|
|
115
|
+
// Only log in debug mode to avoid spam
|
|
116
|
+
if (process.env.LOG_LEVEL?.toLowerCase() === "debug") {
|
|
117
|
+
logger.debug(`Client disconnected: ${socket.id}`);
|
|
118
|
+
}
|
|
119
|
+
this.handleDisconnect(socket);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Handle errors
|
|
123
|
+
socket.on("error", (_error: Error) => {
|
|
124
|
+
logger.error(`Socket error for ${socket.id}:`, _error);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private subscribeToSimulator(socket: Socket, simulatorId: string): void {
|
|
130
|
+
const client = this.clients.get(socket.id);
|
|
131
|
+
if (!client) return;
|
|
132
|
+
|
|
133
|
+
// Add to client's subscriptions
|
|
134
|
+
client.subscriptions.add(simulatorId);
|
|
135
|
+
|
|
136
|
+
// Add to simulator's subscriber list
|
|
137
|
+
if (!this.simulatorSubscriptions.has(simulatorId)) {
|
|
138
|
+
this.simulatorSubscriptions.set(simulatorId, new Set());
|
|
139
|
+
}
|
|
140
|
+
const subscribers = this.simulatorSubscriptions.get(simulatorId);
|
|
141
|
+
if (subscribers) {
|
|
142
|
+
subscribers.add(socket.id);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Join socket.io room for efficient broadcasting
|
|
146
|
+
socket.join(`simulator:${simulatorId}`);
|
|
147
|
+
|
|
148
|
+
logger.debug(`Client ${socket.id} subscribed to simulator ${simulatorId}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private unsubscribeFromSimulator(socket: Socket, simulatorId: string): void {
|
|
152
|
+
const client = this.clients.get(socket.id);
|
|
153
|
+
if (!client) return;
|
|
154
|
+
|
|
155
|
+
// Remove from client's subscriptions
|
|
156
|
+
client.subscriptions.delete(simulatorId);
|
|
157
|
+
|
|
158
|
+
// Remove from simulator's subscriber list
|
|
159
|
+
const subscribers = this.simulatorSubscriptions.get(simulatorId);
|
|
160
|
+
if (subscribers) {
|
|
161
|
+
subscribers.delete(socket.id);
|
|
162
|
+
if (subscribers.size === 0) {
|
|
163
|
+
this.simulatorSubscriptions.delete(simulatorId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Leave socket.io room
|
|
168
|
+
socket.leave(`simulator:${simulatorId}`);
|
|
169
|
+
|
|
170
|
+
logger.debug(
|
|
171
|
+
`Client ${socket.id} unsubscribed from simulator ${simulatorId}`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private handleDisconnect(socket: Socket): void {
|
|
176
|
+
const client = this.clients.get(socket.id);
|
|
177
|
+
if (!client) return;
|
|
178
|
+
|
|
179
|
+
// Clean up all subscriptions
|
|
180
|
+
client.subscriptions.forEach((simulatorId) => {
|
|
181
|
+
const subscribers = this.simulatorSubscriptions.get(simulatorId);
|
|
182
|
+
if (subscribers) {
|
|
183
|
+
subscribers.delete(socket.id);
|
|
184
|
+
if (subscribers.size === 0) {
|
|
185
|
+
this.simulatorSubscriptions.delete(simulatorId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Remove client
|
|
191
|
+
this.clients.delete(socket.id);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Broadcast an event to all connected clients
|
|
196
|
+
*/
|
|
197
|
+
broadcast(event: string, data: BroadcastData): void {
|
|
198
|
+
if (!this.io) return;
|
|
199
|
+
|
|
200
|
+
// Allow tests/consumers to observe outbound broadcasts
|
|
201
|
+
if (this.broadcastHook) {
|
|
202
|
+
try {
|
|
203
|
+
this.broadcastHook(event, data);
|
|
204
|
+
} catch (_err) {
|
|
205
|
+
// ignore hook errors
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Create typed message based on event
|
|
210
|
+
let message: WebSocketMessage;
|
|
211
|
+
// Timestamp removed - not needed in simplified message structure
|
|
212
|
+
|
|
213
|
+
switch (event) {
|
|
214
|
+
case "simulator:started":
|
|
215
|
+
case "simulator:stopped":
|
|
216
|
+
case "simulator:update":
|
|
217
|
+
message = {
|
|
218
|
+
type: event as
|
|
219
|
+
| "simulator:started"
|
|
220
|
+
| "simulator:stopped"
|
|
221
|
+
| "simulator:data",
|
|
222
|
+
data: data as Simulator,
|
|
223
|
+
} as SimulatorUpdateMessage;
|
|
224
|
+
break;
|
|
225
|
+
|
|
226
|
+
case "template:created":
|
|
227
|
+
case "template:updated":
|
|
228
|
+
case "template:deleted":
|
|
229
|
+
message = {
|
|
230
|
+
type: event as
|
|
231
|
+
| "template:created"
|
|
232
|
+
| "template:updated"
|
|
233
|
+
| "template:deleted",
|
|
234
|
+
data: data as Template,
|
|
235
|
+
} as TemplateUpdateMessage;
|
|
236
|
+
break;
|
|
237
|
+
|
|
238
|
+
case "simulator:data":
|
|
239
|
+
message = {
|
|
240
|
+
type: "data:update",
|
|
241
|
+
data: {
|
|
242
|
+
simulatorId: data.simulatorId!,
|
|
243
|
+
values: data.values || { [data.address]: data.value },
|
|
244
|
+
},
|
|
245
|
+
} as DataUpdateMessage;
|
|
246
|
+
break;
|
|
247
|
+
|
|
248
|
+
default:
|
|
249
|
+
message = {
|
|
250
|
+
type: event,
|
|
251
|
+
data: data,
|
|
252
|
+
} as WebSocketMessage;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Broadcast to all clients
|
|
256
|
+
this.io.emit(event, message.data);
|
|
257
|
+
|
|
258
|
+
// If simulator-specific, also send to room
|
|
259
|
+
if (data.simulatorId) {
|
|
260
|
+
this.io.to(`simulator:${data.simulatorId}`).emit(event, message.data);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
logger.debug(`Broadcast ${event} to ${this.clients.size} clients`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Send event to specific simulator subscribers
|
|
268
|
+
*/
|
|
269
|
+
broadcastToSimulator(simulatorId: string, event: string, data: any): void {
|
|
270
|
+
if (!this.io) return;
|
|
271
|
+
|
|
272
|
+
const message: DataUpdateMessage = {
|
|
273
|
+
type: "data:update",
|
|
274
|
+
data: {
|
|
275
|
+
simulatorId,
|
|
276
|
+
values: data,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
this.io.to(`simulator:${simulatorId}`).emit(event, message.data);
|
|
281
|
+
|
|
282
|
+
const subscriberCount =
|
|
283
|
+
this.simulatorSubscriptions.get(simulatorId)?.size || 0;
|
|
284
|
+
logger.debug(
|
|
285
|
+
`Broadcast ${event} to ${subscriberCount} subscribers of simulator ${simulatorId}`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get statistics about connected clients
|
|
291
|
+
*/
|
|
292
|
+
getStats(): {
|
|
293
|
+
totalClients: number;
|
|
294
|
+
totalSubscriptions: number;
|
|
295
|
+
simulatorSubscriptions: Record<string, number>;
|
|
296
|
+
} {
|
|
297
|
+
const stats = {
|
|
298
|
+
totalClients: this.clients.size,
|
|
299
|
+
totalSubscriptions: 0,
|
|
300
|
+
simulatorSubscriptions: {} as Record<string, number>,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Count total subscriptions
|
|
304
|
+
this.clients.forEach((client) => {
|
|
305
|
+
stats.totalSubscriptions += client.subscriptions.size;
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Count per-simulator subscriptions
|
|
309
|
+
this.simulatorSubscriptions.forEach((subscribers, simulatorId) => {
|
|
310
|
+
stats.simulatorSubscriptions[simulatorId] = subscribers.size;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return stats;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get list of connected clients
|
|
318
|
+
*/
|
|
319
|
+
getClients(): ClientInfo[] {
|
|
320
|
+
return Array.from(this.clients.values());
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Check if server is initialized
|
|
325
|
+
*/
|
|
326
|
+
isInitialized(): boolean {
|
|
327
|
+
return this.io !== null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Shutdown the WebSocket server
|
|
332
|
+
*/
|
|
333
|
+
async shutdown(): Promise<void> {
|
|
334
|
+
if (this.io) {
|
|
335
|
+
// Disconnect all clients
|
|
336
|
+
this.io.disconnectSockets();
|
|
337
|
+
|
|
338
|
+
// Close the server
|
|
339
|
+
await new Promise<void>((resolve) => {
|
|
340
|
+
this.io!.close(() => {
|
|
341
|
+
logger.info("WebSocket server shut down");
|
|
342
|
+
resolve();
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
this.io = null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Clear all data
|
|
350
|
+
this.clients.clear();
|
|
351
|
+
this.simulatorSubscriptions.clear();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Singleton instance
|
|
356
|
+
let wsServer: WebSocketServer | null = null;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Create WebSocket server instance
|
|
360
|
+
*/
|
|
361
|
+
export function createWebSocketServer(
|
|
362
|
+
httpServer: HTTPServer,
|
|
363
|
+
opts?: {
|
|
364
|
+
reset?: boolean;
|
|
365
|
+
broadcastHook?: (event: string, data: any) => void;
|
|
366
|
+
},
|
|
367
|
+
): WebSocketServer {
|
|
368
|
+
// Initialize logger if not already done
|
|
369
|
+
if (!logger) {
|
|
370
|
+
logger = createLogger("WebSocket");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!wsServer || opts?.reset) {
|
|
374
|
+
wsServer = new WebSocketServer(httpServer);
|
|
375
|
+
wsServer.initialize({ broadcastHook: opts?.broadcastHook });
|
|
376
|
+
} else if (opts?.broadcastHook) {
|
|
377
|
+
// Reconfigure hook without rebuilding the IO server
|
|
378
|
+
wsServer.initialize({ broadcastHook: opts.broadcastHook });
|
|
379
|
+
}
|
|
380
|
+
return wsServer;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get existing WebSocket server instance
|
|
385
|
+
*/
|
|
386
|
+
export function getWebSocketServer(): WebSocketServer | null {
|
|
387
|
+
return wsServer;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Export the class for type usage
|
|
392
|
+
*/
|
|
393
|
+
export { WebSocketServer };
|
|
394
|
+
export type { ClientInfo, BroadcastData };
|