wse-client 1.0.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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -0
  3. package/dist/constants.d.ts +195 -0
  4. package/dist/constants.d.ts.map +1 -0
  5. package/dist/constants.js +285 -0
  6. package/dist/constants.js.map +1 -0
  7. package/dist/examples/ChatHandlers.d.ts +15 -0
  8. package/dist/examples/ChatHandlers.d.ts.map +1 -0
  9. package/dist/examples/ChatHandlers.js +20 -0
  10. package/dist/examples/ChatHandlers.js.map +1 -0
  11. package/dist/examples/IoTHandlers.d.ts +12 -0
  12. package/dist/examples/IoTHandlers.d.ts.map +1 -0
  13. package/dist/examples/IoTHandlers.js +15 -0
  14. package/dist/examples/IoTHandlers.js.map +1 -0
  15. package/dist/handlers/EventHandlers.d.ts +17 -0
  16. package/dist/handlers/EventHandlers.d.ts.map +1 -0
  17. package/dist/handlers/EventHandlers.js +20 -0
  18. package/dist/handlers/EventHandlers.js.map +1 -0
  19. package/dist/handlers/index.d.ts +14 -0
  20. package/dist/handlers/index.d.ts.map +1 -0
  21. package/dist/handlers/index.js +37 -0
  22. package/dist/handlers/index.js.map +1 -0
  23. package/dist/hooks/useWSE.d.ts +42 -0
  24. package/dist/hooks/useWSE.d.ts.map +1 -0
  25. package/dist/hooks/useWSE.js +1224 -0
  26. package/dist/hooks/useWSE.js.map +1 -0
  27. package/dist/index.d.ts +25 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +48 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/protocols/compression.d.ts +17 -0
  32. package/dist/protocols/compression.d.ts.map +1 -0
  33. package/dist/protocols/compression.js +155 -0
  34. package/dist/protocols/compression.js.map +1 -0
  35. package/dist/protocols/transformer.d.ts +12 -0
  36. package/dist/protocols/transformer.d.ts.map +1 -0
  37. package/dist/protocols/transformer.js +76 -0
  38. package/dist/protocols/transformer.js.map +1 -0
  39. package/dist/services/AdaptiveQualityManager.d.ts +44 -0
  40. package/dist/services/AdaptiveQualityManager.d.ts.map +1 -0
  41. package/dist/services/AdaptiveQualityManager.js +114 -0
  42. package/dist/services/AdaptiveQualityManager.js.map +1 -0
  43. package/dist/services/ConnectionManager.d.ts +92 -0
  44. package/dist/services/ConnectionManager.d.ts.map +1 -0
  45. package/dist/services/ConnectionManager.js +852 -0
  46. package/dist/services/ConnectionManager.js.map +1 -0
  47. package/dist/services/ConnectionPool.d.ts +74 -0
  48. package/dist/services/ConnectionPool.d.ts.map +1 -0
  49. package/dist/services/ConnectionPool.js +430 -0
  50. package/dist/services/ConnectionPool.js.map +1 -0
  51. package/dist/services/EventSequencer.d.ts +30 -0
  52. package/dist/services/EventSequencer.d.ts.map +1 -0
  53. package/dist/services/EventSequencer.js +152 -0
  54. package/dist/services/EventSequencer.js.map +1 -0
  55. package/dist/services/MessageProcessor.d.ts +69 -0
  56. package/dist/services/MessageProcessor.d.ts.map +1 -0
  57. package/dist/services/MessageProcessor.js +1050 -0
  58. package/dist/services/MessageProcessor.js.map +1 -0
  59. package/dist/services/NetworkMonitor.d.ts +34 -0
  60. package/dist/services/NetworkMonitor.d.ts.map +1 -0
  61. package/dist/services/NetworkMonitor.js +143 -0
  62. package/dist/services/NetworkMonitor.js.map +1 -0
  63. package/dist/services/OfflineQueue.d.ts +25 -0
  64. package/dist/services/OfflineQueue.d.ts.map +1 -0
  65. package/dist/services/OfflineQueue.js +117 -0
  66. package/dist/services/OfflineQueue.js.map +1 -0
  67. package/dist/services/RateLimiter.d.ts +25 -0
  68. package/dist/services/RateLimiter.d.ts.map +1 -0
  69. package/dist/services/RateLimiter.js +85 -0
  70. package/dist/services/RateLimiter.js.map +1 -0
  71. package/dist/stores/useMessageQueueStore.d.ts +23 -0
  72. package/dist/stores/useMessageQueueStore.d.ts.map +1 -0
  73. package/dist/stores/useMessageQueueStore.js +188 -0
  74. package/dist/stores/useMessageQueueStore.js.map +1 -0
  75. package/dist/stores/useWSEStore.d.ts +54 -0
  76. package/dist/stores/useWSEStore.d.ts.map +1 -0
  77. package/dist/stores/useWSEStore.js +464 -0
  78. package/dist/stores/useWSEStore.js.map +1 -0
  79. package/dist/types.d.ts +403 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +39 -0
  82. package/dist/types.js.map +1 -0
  83. package/dist/utils/circuitBreaker.d.ts +35 -0
  84. package/dist/utils/circuitBreaker.d.ts.map +1 -0
  85. package/dist/utils/circuitBreaker.js +145 -0
  86. package/dist/utils/circuitBreaker.js.map +1 -0
  87. package/dist/utils/logger.d.ts +46 -0
  88. package/dist/utils/logger.d.ts.map +1 -0
  89. package/dist/utils/logger.js +231 -0
  90. package/dist/utils/logger.js.map +1 -0
  91. package/dist/utils/security.d.ts +74 -0
  92. package/dist/utils/security.d.ts.map +1 -0
  93. package/dist/utils/security.js +383 -0
  94. package/dist/utils/security.js.map +1 -0
  95. package/package.json +63 -0
@@ -0,0 +1,1050 @@
1
+ // =============================================================================
2
+ // WebSocket Event System - Message Processing
3
+ // =============================================================================
4
+ import { MessagePriority, ConnectionQuality, ConnectionState, CircuitBreakerState } from '../types';
5
+ import { useWSEStore } from '../stores/useWSEStore';
6
+ import { useMessageQueueStore } from '../stores/useMessageQueueStore';
7
+ import { EventSequencer } from './EventSequencer';
8
+ import { CompressionManager } from '../protocols/compression';
9
+ import { logger } from '../utils/logger';
10
+ import { WS_CLIENT_VERSION, WS_PROTOCOL_VERSION } from '../constants';
11
+ import { securityManager } from '../utils/security';
12
+ export class MessageProcessor {
13
+ constructor(batchSize = 10, batchTimeout = 100) {
14
+ this.batchSize = batchSize;
15
+ this.batchTimeout = batchTimeout;
16
+ this.batchTimer = null;
17
+ this.processing = false;
18
+ this.connectionManager = null;
19
+ this.isReady = false;
20
+ // Use promise-based queue for race condition prevention
21
+ this.batchPromise = null;
22
+ this.destroyed = false;
23
+ // Add server ready state management
24
+ this.serverReadyProcessed = false;
25
+ this.serverReadyDetails = null;
26
+ // High-frequency event types that use debug-level logging instead of info
27
+ this.highFrequencyTypes = new Set([
28
+ 'heartbeat',
29
+ 'health_check',
30
+ 'health_check_response',
31
+ 'metrics_response',
32
+ 'PONG',
33
+ ]);
34
+ this.sequencer = new EventSequencer();
35
+ this.compression = new CompressionManager();
36
+ this.messageHandlers = new Map();
37
+ this.registerDefaultHandlers();
38
+ }
39
+ setConnectionManager(manager) {
40
+ this.connectionManager = manager;
41
+ // Process pending server ready if we have it
42
+ if (this.serverReadyProcessed && this.serverReadyDetails && manager) {
43
+ logger.info('Processing pending server ready details');
44
+ manager.handleServerReady(this.serverReadyDetails);
45
+ this.serverReadyDetails = null; // Clear after processing
46
+ }
47
+ }
48
+ setReady(ready) {
49
+ this.isReady = ready;
50
+ if (ready) {
51
+ logger.info('MessageProcessor is now ready to process messages');
52
+ }
53
+ }
54
+ // ---------------------------------------------------------------------------
55
+ // Debug
56
+ // ---------------------------------------------------------------------------
57
+ debugMessage(type, message, details) {
58
+ // Only log in development or if debug mode is enabled
59
+ if (process.env.NODE_ENV !== 'development' && !window.WSE_DEBUG) {
60
+ return;
61
+ }
62
+ const timestamp = new Date().toISOString();
63
+ const logEntry = {
64
+ timestamp,
65
+ type,
66
+ message: {
67
+ t: message?.t,
68
+ id: message?.id,
69
+ seq: message?.seq,
70
+ hasPayload: !!message?.p,
71
+ payloadType: typeof message?.p,
72
+ payloadKeys: message?.p ? Object.keys(message.p) : [],
73
+ },
74
+ details,
75
+ raw: message
76
+ };
77
+ // Store in session storage for debugging
78
+ try {
79
+ const debugLog = JSON.parse(sessionStorage.getItem('wse_debug_log') || '[]');
80
+ debugLog.push(logEntry);
81
+ // Keep only last 100 entries
82
+ if (debugLog.length > 100) {
83
+ debugLog.shift();
84
+ }
85
+ sessionStorage.setItem('wse_debug_log', JSON.stringify(debugLog));
86
+ }
87
+ catch (e) {
88
+ // Ignore storage errors
89
+ }
90
+ // Log to console with color coding
91
+ const color = type.includes('error') ? 'color: red' :
92
+ type.includes('warning') ? 'color: orange' :
93
+ 'color: blue';
94
+ console.log(`%c[WSE Debug] ${type}`, color, logEntry);
95
+ }
96
+ // Add this static method to enable/disable debug mode
97
+ static enableDebugMode(enabled = true) {
98
+ window.WSE_DEBUG = enabled;
99
+ if (enabled) {
100
+ logger.info('WSE Debug mode enabled. Messages will be logged to console and sessionStorage.');
101
+ logger.info('View debug log with: JSON.parse(sessionStorage.getItem("wse_debug_log"))');
102
+ logger.info('Clear debug log with: sessionStorage.removeItem("wse_debug_log")');
103
+ }
104
+ else {
105
+ logger.info('WSE Debug mode disabled.');
106
+ }
107
+ }
108
+ // ---------------------------------------------------------------------------
109
+ // Event Versioning
110
+ // ---------------------------------------------------------------------------
111
+ handleEventVersion(message) {
112
+ // Client supports event versions 1-2
113
+ const CLIENT_SUPPORTED_VERSION = 2;
114
+ if (message.event_version && message.event_version > CLIENT_SUPPORTED_VERSION) {
115
+ logger.warn(`Received event with version ${message.event_version} (type: ${message.t}), ` +
116
+ `but client only supports up to version ${CLIENT_SUPPORTED_VERSION}. ` +
117
+ `Some fields may not be handled correctly. Consider upgrading the client.`, { eventType: message.t, version: message.event_version });
118
+ }
119
+ // Log version info for debugging (only once per event type)
120
+ if (message.event_version) {
121
+ const key = `${message.t}_v${message.event_version}`;
122
+ if (!this.sequencer.isDuplicate(key)) {
123
+ logger.debug(`Event type '${message.t}' using schema version ${message.event_version}`);
124
+ }
125
+ }
126
+ }
127
+ logPerformanceMetrics(message) {
128
+ // Log performance metrics for observability and monitoring
129
+ const metrics = [];
130
+ if (message.latency_ms !== undefined) {
131
+ metrics.push(`event_latency=${message.latency_ms}ms`);
132
+ // Warn on high end-to-end latency
133
+ if (message.latency_ms > 1000) {
134
+ logger.warn(`High event latency detected: ${message.latency_ms}ms for event type '${message.t}'`, { eventType: message.t, latency: message.latency_ms });
135
+ }
136
+ }
137
+ if (message.wse_processing_ms !== undefined) {
138
+ metrics.push(`wse_processing=${message.wse_processing_ms}ms`);
139
+ if (message.wse_processing_ms > 150) {
140
+ logger.warn(`High backend WSE processing time: ${message.wse_processing_ms}ms for event type '${message.t}'`, {
141
+ eventType: message.t,
142
+ backendWseProcessing: message.wse_processing_ms,
143
+ note: 'This is backend processing time, not frontend processing'
144
+ });
145
+ }
146
+ }
147
+ if (message.trace_id) {
148
+ metrics.push(`trace_id=${message.trace_id}`);
149
+ }
150
+ // Log metrics if any are present
151
+ if (metrics.length > 0) {
152
+ logger.debug(`Event '${message.t}' metrics: ${metrics.join(', ')}`);
153
+ }
154
+ }
155
+ // ---------------------------------------------------------------------------
156
+ // Message Processing
157
+ // ---------------------------------------------------------------------------
158
+ async processIncoming(data) {
159
+ if (this.destroyed)
160
+ return;
161
+ const store = useWSEStore.getState();
162
+ store.incrementMetric('messagesReceived');
163
+ try {
164
+ let message;
165
+ if (data instanceof ArrayBuffer) {
166
+ message = await this.processBinaryMessage(data);
167
+ store.incrementMetric('bytesReceived', data.byteLength);
168
+ }
169
+ else {
170
+ message = this.processTextMessage(data);
171
+ store.incrementMetric('bytesReceived', new TextEncoder().encode(data).byteLength);
172
+ }
173
+ if (!message)
174
+ return;
175
+ // Log every message for debugging
176
+ logger.debug(`Received message type: ${message.t}`, message);
177
+ // Check event version compatibility
178
+ this.handleEventVersion(message);
179
+ // Log performance metrics for observability
180
+ this.logPerformanceMetrics(message);
181
+ // Check for duplicate
182
+ if (message.id && this.sequencer.isDuplicate(message.id)) {
183
+ logger.debug(`Duplicate message ignored: ${message.id}`);
184
+ return;
185
+ }
186
+ // Record sequence if present
187
+ if (message.seq !== undefined) {
188
+ this.sequencer.recordSequence(message.seq);
189
+ }
190
+ // Route message
191
+ await this.routeMessage(message);
192
+ }
193
+ catch (error) {
194
+ logger.error('Error processing message:', error);
195
+ store.setLastError('Message processing error');
196
+ }
197
+ }
198
+ processTextMessage(data) {
199
+ // Handle special text messages from the backend
200
+ // WSE:PING from backend - respond with JSON PONG
201
+ if (data.toUpperCase().startsWith('WSE:PING') || data.toUpperCase().startsWith('PING')) {
202
+ const parts = data.split(':');
203
+ const serverTimestamp = parts.length > 1 ? parseInt(parts[parts.length - 1], 10) : Date.now();
204
+ if (this.connectionManager?.ws?.readyState === WebSocket.OPEN) {
205
+ try {
206
+ const pongMessage = {
207
+ t: 'PONG',
208
+ p: {
209
+ server_timestamp: serverTimestamp,
210
+ client_timestamp: Date.now()
211
+ },
212
+ v: WS_PROTOCOL_VERSION
213
+ };
214
+ this.connectionManager.ws.send(`WSE${JSON.stringify(pongMessage)}`);
215
+ logger.debug(`Responded to WSE:PING with JSON PONG`);
216
+ }
217
+ catch (error) {
218
+ logger.error('Failed to send PONG:', error);
219
+ }
220
+ }
221
+ return null;
222
+ }
223
+ // WSE:PONG text response (legacy, keep for backward compatibility)
224
+ if (data.startsWith('WSE:PONG:') || data.startsWith('PONG:')) {
225
+ const timestamp = parseInt(data.split(':').pop() || '0', 10);
226
+ const latency = Date.now() - timestamp;
227
+ const store = useWSEStore.getState();
228
+ store.recordLatency(latency);
229
+ logger.debug(`WSE:PONG latency: ${latency}ms`);
230
+ return null;
231
+ }
232
+ // Parse message category prefix (WSE=System, S=Snapshot, U=Update)
233
+ let msgCategory = null;
234
+ let jsonData = data;
235
+ if (data.startsWith('WSE{')) {
236
+ msgCategory = 'WSE';
237
+ jsonData = data.substring(3);
238
+ }
239
+ else if (data.startsWith('S{')) {
240
+ msgCategory = 'S';
241
+ jsonData = data.substring(1);
242
+ }
243
+ else if (data.startsWith('U{')) {
244
+ msgCategory = 'U';
245
+ jsonData = data.substring(1);
246
+ }
247
+ try {
248
+ const parsed = JSON.parse(jsonData);
249
+ if (msgCategory) {
250
+ parsed._msg_cat = msgCategory;
251
+ }
252
+ return parsed;
253
+ }
254
+ catch (error) {
255
+ logger.error('Invalid JSON message:', error);
256
+ return null;
257
+ }
258
+ }
259
+ async processBinaryMessage(data) {
260
+ const view = new Uint8Array(data);
261
+ // Log binary message details for debugging
262
+ logger.debug('Binary message received:', {
263
+ length: data.byteLength,
264
+ first10Bytes: Array.from(view.slice(0, 10)),
265
+ first2Chars: view.length >= 2 ? String.fromCharCode(view[0], view[1]) : 'N/A',
266
+ hexFirst10: Array.from(view.slice(0, 10)).map(b => b.toString(16).padStart(2, '0')).join(' ')
267
+ });
268
+ // Check for zlib magic bytes FIRST
269
+ if (view.length > 2 && view[0] === 0x78) {
270
+ const zlibCompressionMethods = [0x01, 0x5E, 0x9C, 0xDA];
271
+ if (zlibCompressionMethods.includes(view[1])) {
272
+ try {
273
+ logger.debug('Detected raw zlib compressed data (0x78 header)');
274
+ const decompressed = this.compression.decompress(data);
275
+ const text = new TextDecoder().decode(decompressed);
276
+ const parsed = JSON.parse(text);
277
+ logger.debug('Decompressed raw zlib message:', {
278
+ type: parsed.t,
279
+ originalSize: data.byteLength,
280
+ decompressedSize: decompressed.byteLength
281
+ });
282
+ const store = useWSEStore.getState();
283
+ store.incrementMetric('compressionHits');
284
+ return parsed;
285
+ }
286
+ catch (error) {
287
+ logger.error('Failed to decompress raw zlib data:', error);
288
+ logger.error('Data info:', {
289
+ length: data.byteLength,
290
+ first20Hex: Array.from(view.slice(0, 20)).map(b => b.toString(16).padStart(2, '0')).join(' ')
291
+ });
292
+ // Don't return here - try other methods
293
+ }
294
+ }
295
+ }
296
+ // 1. Check compression header 'C':
297
+ if (view.length >= 2 && view[0] === 67 && view[1] === 58) { // 'C:'
298
+ const store = useWSEStore.getState();
299
+ store.incrementMetric('compressionHits');
300
+ try {
301
+ const compressed = data.slice(2);
302
+ const decompressed = this.compression.decompress(compressed);
303
+ let text = new TextDecoder().decode(decompressed);
304
+ // Strip WSE/S/U prefix from decompressed text
305
+ if (text.startsWith('WSE{')) {
306
+ text = text.substring(3);
307
+ }
308
+ else if (text.startsWith('S{') || text.startsWith('U{')) {
309
+ text = text.substring(1);
310
+ }
311
+ const parsed = JSON.parse(text);
312
+ logger.info('=== DECOMPRESSED MESSAGE WITH C: HEADER ===');
313
+ logger.info('Type:', parsed.t);
314
+ return parsed;
315
+ }
316
+ catch (error) {
317
+ logger.error('Failed to process compressed message with C: header:', error);
318
+ return null;
319
+ }
320
+ }
321
+ // 2. Check MessagePack header 'M':
322
+ if (view.length >= 2 && view[0] === 77 && view[1] === 58) { // 'M:'
323
+ try {
324
+ return this.compression.unpackMsgPack(data.slice(2));
325
+ }
326
+ catch (error) {
327
+ logger.error('Failed to unpack MessagePack:', error);
328
+ return null;
329
+ }
330
+ }
331
+ // 3. Check encryption header 'E:' -- decrypt via SecurityManager
332
+ if (view.length >= 2 && view[0] === 69 && view[1] === 58) { // 'E:'
333
+ if (!securityManager.isEncryptionEnabled()) {
334
+ logger.warn('Encrypted message received but encryption not enabled - message dropped. Size:', data.byteLength);
335
+ return null;
336
+ }
337
+ try {
338
+ const decrypted = await securityManager.decryptFromTransport(data);
339
+ if (!decrypted) {
340
+ logger.error('Decryption returned null for encrypted message');
341
+ return null;
342
+ }
343
+ return this.processTextMessage(decrypted);
344
+ }
345
+ catch (error) {
346
+ logger.error('Failed to decrypt message:', error);
347
+ return null;
348
+ }
349
+ }
350
+ // 4. Try as JSON first
351
+ try {
352
+ const text = new TextDecoder().decode(data);
353
+ if (text.startsWith('{') || text.startsWith('[')) {
354
+ const parsed = JSON.parse(text);
355
+ logger.debug('Parsed as plain JSON:', parsed.t);
356
+ return parsed;
357
+ }
358
+ }
359
+ catch {
360
+ // Not JSON, continue to other methods
361
+ }
362
+ // 5. Try as raw MessagePack
363
+ try {
364
+ const unpacked = this.compression.unpackMsgPack(data);
365
+ logger.debug('Parsed as raw MessagePack');
366
+ return unpacked;
367
+ }
368
+ catch (error) {
369
+ // Not MessagePack either
370
+ }
371
+ // 6. Last attempt - unknown format
372
+ logger.error('Failed to parse binary message in any known format');
373
+ logger.error('Message info:', {
374
+ length: data.byteLength,
375
+ first20Bytes: Array.from(view.slice(0, Math.min(20, view.length))),
376
+ first20Hex: Array.from(view.slice(0, Math.min(20, view.length))).map(b => `0x${b.toString(16).padStart(2, '0')}`).join(' '),
377
+ asText: (() => {
378
+ try {
379
+ const text = new TextDecoder().decode(data.slice(0, Math.min(100, data.byteLength)));
380
+ return text.replace(/[^\x20-\x7E]/g, '.');
381
+ }
382
+ catch {
383
+ return 'Not text data';
384
+ }
385
+ })()
386
+ });
387
+ return null;
388
+ }
389
+ // ---------------------------------------------------------------------------
390
+ // Message Routing
391
+ // ---------------------------------------------------------------------------
392
+ async routeMessage(message) {
393
+ const type = message.t;
394
+ // Handle JSON PING from backend - respond with JSON PONG
395
+ if (type === 'PING' || type === 'ping') {
396
+ if (this.connectionManager?.ws?.readyState === WebSocket.OPEN) {
397
+ try {
398
+ const serverTimestamp = message.p?.timestamp || Date.now();
399
+ const pongMessage = {
400
+ t: 'PONG',
401
+ p: {
402
+ server_timestamp: serverTimestamp,
403
+ client_timestamp: Date.now()
404
+ },
405
+ v: WS_PROTOCOL_VERSION
406
+ };
407
+ this.connectionManager.ws.send(`WSE${JSON.stringify(pongMessage)}`);
408
+ logger.debug(`Responded to JSON PING with PONG`);
409
+ }
410
+ catch (error) {
411
+ logger.error('Failed to send PONG:', error);
412
+ }
413
+ }
414
+ return;
415
+ }
416
+ // Log messages (use debug for high-frequency event types)
417
+ if (this.highFrequencyTypes.has(type)) {
418
+ logger.debug(`Routing: ${type}`);
419
+ }
420
+ else {
421
+ logger.info(`Routing message: ${type}`);
422
+ }
423
+ // Check for a registered handler
424
+ const handler = this.messageHandlers.get(type);
425
+ if (handler) {
426
+ try {
427
+ if (this.highFrequencyTypes.has(type)) {
428
+ logger.debug(`Handler: ${type}`);
429
+ }
430
+ else {
431
+ logger.info(`Executing handler for ${type}`);
432
+ }
433
+ handler(message);
434
+ if (!this.highFrequencyTypes.has(type)) {
435
+ logger.info(`Handler executed successfully for ${type}`);
436
+ }
437
+ }
438
+ catch (error) {
439
+ logger.error(`Error in handler for ${type}:`, error);
440
+ }
441
+ return;
442
+ }
443
+ // If no handler found
444
+ logger.warn(`NO HANDLER FOUND for message type: ${type}`);
445
+ logger.warn(`Registered handlers:`, Array.from(this.messageHandlers.keys()));
446
+ }
447
+ // ---------------------------------------------------------------------------
448
+ // Message Handlers
449
+ // ---------------------------------------------------------------------------
450
+ registerDefaultHandlers() {
451
+ logger.info('Registering default message handlers');
452
+ // System message handlers
453
+ this.messageHandlers.set('server_ready', (msg) => this.handleServerReady(msg));
454
+ this.messageHandlers.set('server_hello', (msg) => this.handleServerHello(msg));
455
+ this.messageHandlers.set('subscription_update', (msg) => this.handleSubscriptionUpdate(msg));
456
+ this.messageHandlers.set('error', (msg) => this.handleError(msg));
457
+ this.messageHandlers.set('connection_state_change', (msg) => this.handleConnectionStateChange(msg));
458
+ this.messageHandlers.set('health_check', (msg) => this.handleHealthCheck(msg));
459
+ this.messageHandlers.set('health_check_response', (msg) => this.handleHealthCheckResponse(msg));
460
+ this.messageHandlers.set('rate_limit_warning', (msg) => this.handleRateLimitWarning(msg));
461
+ this.messageHandlers.set('connection_quality', (msg) => this.handleConnectionQuality(msg));
462
+ this.messageHandlers.set('snapshot_complete', (msg) => this.handleSnapshotComplete(msg));
463
+ this.messageHandlers.set('heartbeat', () => this.handleHeartbeat());
464
+ this.messageHandlers.set('PONG', () => { }); // PONG is handled in processTextMessage
465
+ // Debug handlers
466
+ this.messageHandlers.set('debug_response', (msg) => this.handleDebugResponse(msg));
467
+ this.messageHandlers.set('sequence_stats_response', (msg) => this.handleSequenceStatsResponse(msg));
468
+ // Configuration handlers
469
+ this.messageHandlers.set('config_response', (msg) => this.handleConfigResponse(msg));
470
+ this.messageHandlers.set('config_update_response', (msg) => this.handleConfigUpdateResponse(msg));
471
+ // Encryption handlers
472
+ this.messageHandlers.set('encryption_response', (msg) => this.handleEncryptionResponse(msg));
473
+ this.messageHandlers.set('key_rotation_response', (msg) => this.handleKeyRotationResponse(msg));
474
+ // Batch handlers
475
+ this.messageHandlers.set('batch', (msg) => this.handleBatchMessage(msg));
476
+ this.messageHandlers.set('batch_message_result', (msg) => this.handleBatchMessageResult(msg));
477
+ // Metrics handler
478
+ this.messageHandlers.set('metrics_response', (msg) => this.handleMetricsResponse(msg));
479
+ // Connection state handler
480
+ this.messageHandlers.set('connection_state_response', (msg) => this.handleConnectionStateResponse(msg));
481
+ // Sync request handler
482
+ this.messageHandlers.set('sync_request', (msg) => {
483
+ const store = useWSEStore.getState();
484
+ this.queueOutgoing({
485
+ t: 'sync_response',
486
+ p: {
487
+ client_version: WS_CLIENT_VERSION,
488
+ protocol_version: WS_PROTOCOL_VERSION,
489
+ sequence: this.sequencer.getCurrentSequence(),
490
+ subscriptions: store.activeTopics,
491
+ last_update: Date.now(),
492
+ }
493
+ }, MessagePriority.HIGH);
494
+ });
495
+ // Config update handler
496
+ this.messageHandlers.set('config_update', (msg) => {
497
+ const config = msg.p;
498
+ const store = useWSEStore.getState();
499
+ if (config.compression_enabled !== undefined) {
500
+ store.updateConfig({ compressionEnabled: config.compression_enabled });
501
+ }
502
+ if (config.batching_enabled !== undefined) {
503
+ store.updateConfig({ batchingEnabled: config.batching_enabled });
504
+ if (config.batch_size) {
505
+ this.batchSize = config.batch_size;
506
+ }
507
+ if (config.batch_timeout) {
508
+ this.batchTimeout = config.batch_timeout;
509
+ }
510
+ }
511
+ if (config.max_queue_size !== undefined) {
512
+ const queueStore = useMessageQueueStore.getState();
513
+ queueStore.setCapacity(config.max_queue_size);
514
+ }
515
+ logger.info('Configuration updated:', config);
516
+ });
517
+ // Metrics request handler
518
+ this.messageHandlers.set('metrics_request', (msg) => {
519
+ const store = useWSEStore.getState();
520
+ const queueStore = useMessageQueueStore.getState();
521
+ this.queueOutgoing({
522
+ t: 'metrics_response',
523
+ p: {
524
+ client_version: WS_CLIENT_VERSION,
525
+ connection_stats: store.metrics,
526
+ queue_stats: queueStore.stats,
527
+ diagnostics: store.diagnostics,
528
+ circuit_breaker: store.circuitBreaker,
529
+ security: store.security,
530
+ subscriptions: {
531
+ active: store.activeTopics,
532
+ pending: store.subscriptions.pendingSubscriptions,
533
+ failed: store.subscriptions.failedSubscriptions,
534
+ },
535
+ event_sequencer: this.sequencer.getStats(),
536
+ timestamp: new Date().toISOString(),
537
+ }
538
+ }, MessagePriority.HIGH);
539
+ });
540
+ // Priority message handler
541
+ this.messageHandlers.set('priority_message', (msg) => {
542
+ const payload = msg.p;
543
+ const priority = payload.priority || MessagePriority.NORMAL;
544
+ logger.info(`Priority message received with priority ${priority}:`, payload);
545
+ if (payload.type && this.messageHandlers.has(payload.type)) {
546
+ const handler = this.messageHandlers.get(payload.type);
547
+ handler({ ...msg, p: payload.content || payload });
548
+ }
549
+ });
550
+ logger.info('Default handlers registered');
551
+ }
552
+ handleServerReady(message) {
553
+ const payload = message.p;
554
+ logger.info('Server ready:', payload);
555
+ const store = useWSEStore.getState();
556
+ // Set connection state to CONNECTED
557
+ store.setConnectionState(ConnectionState.CONNECTED);
558
+ store.updateMetrics({ connectedSince: Date.now() });
559
+ // Store server ready details for later processing
560
+ this.serverReadyDetails = payload.details || payload;
561
+ this.serverReadyProcessed = true;
562
+ // Process when connection manager is available
563
+ if (this.connectionManager) {
564
+ this.connectionManager.handleServerReady(this.serverReadyDetails);
565
+ this.serverReadyDetails = null;
566
+ }
567
+ else {
568
+ logger.info('Connection manager not yet available, storing server ready details for later');
569
+ }
570
+ }
571
+ processPendingServerReady() {
572
+ if (this.serverReadyProcessed && this.serverReadyDetails && this.connectionManager) {
573
+ this.connectionManager.handleServerReady(this.serverReadyDetails);
574
+ this.serverReadyDetails = null;
575
+ }
576
+ }
577
+ resetServerReadyFlag() {
578
+ this.serverReadyProcessed = false;
579
+ this.serverReadyDetails = null;
580
+ }
581
+ handleServerHello(message) {
582
+ const payload = message.p;
583
+ logger.info('Server hello received:', payload);
584
+ const store = useWSEStore.getState();
585
+ if (payload.features) {
586
+ store.updateConfig({
587
+ compressionEnabled: payload.features.compression ?? true,
588
+ batchingEnabled: payload.features.batching ?? true,
589
+ offlineModeEnabled: payload.features.offline_queue ?? true,
590
+ });
591
+ }
592
+ if (payload.limits) {
593
+ const queueStore = useMessageQueueStore.getState();
594
+ if (payload.limits.max_queue_size) {
595
+ queueStore.setCapacity(payload.limits.max_queue_size);
596
+ }
597
+ }
598
+ }
599
+ handleConnectionStateChange(message) {
600
+ const payload = message.p;
601
+ logger.info('Connection state change:', payload);
602
+ const store = useWSEStore.getState();
603
+ if (payload.new_state) {
604
+ const stateMap = {
605
+ 'pending': ConnectionState.PENDING,
606
+ 'connecting': ConnectionState.CONNECTING,
607
+ 'connected': ConnectionState.CONNECTED,
608
+ 'reconnecting': ConnectionState.RECONNECTING,
609
+ 'disconnected': ConnectionState.DISCONNECTED,
610
+ 'error': ConnectionState.ERROR,
611
+ 'degraded': ConnectionState.DEGRADED,
612
+ };
613
+ const newState = stateMap[payload.new_state.toLowerCase()];
614
+ if (newState) {
615
+ store.setConnectionState(newState);
616
+ }
617
+ }
618
+ window.dispatchEvent(new CustomEvent('connectionStateChange', {
619
+ detail: {
620
+ oldState: payload.old_state,
621
+ newState: payload.new_state,
622
+ connectionId: payload.connection_id,
623
+ timestamp: payload.timestamp || new Date().toISOString(),
624
+ }
625
+ }));
626
+ }
627
+ handleSubscriptionUpdate(message) {
628
+ const payload = message.p;
629
+ const store = useWSEStore.getState();
630
+ logger.info('=== SUBSCRIPTION UPDATE RECEIVED ===');
631
+ logger.info('Payload:', payload);
632
+ if (payload.success) {
633
+ payload.success_topics?.forEach((topic) => {
634
+ store.confirmSubscription(topic);
635
+ });
636
+ }
637
+ if (payload.failed_topics) {
638
+ payload.failed_topics.forEach((topic) => {
639
+ store.failSubscription(topic);
640
+ });
641
+ }
642
+ window.dispatchEvent(new CustomEvent('subscriptionUpdate', {
643
+ detail: payload
644
+ }));
645
+ }
646
+ handleError(message) {
647
+ const store = useWSEStore.getState();
648
+ logger.error('Server error received:', {
649
+ type: message.t,
650
+ payload: message.p,
651
+ fullMessage: JSON.stringify(message, null, 2)
652
+ });
653
+ const errorData = message.p || {};
654
+ const errorMessage = errorData.message || 'Unknown error';
655
+ const errorCode = errorData.code || 'UNKNOWN_ERROR';
656
+ const recoverable = errorData.recoverable !== false;
657
+ const details = errorData.details || {};
658
+ logger.error(`Processed error - Code: ${errorCode}, Message: ${errorMessage}`, {
659
+ errorData,
660
+ recoverable,
661
+ details
662
+ });
663
+ store.setLastError(errorMessage, typeof errorCode === 'number' ? errorCode :
664
+ errorCode === 'AUTH_FAILED' ? 401 :
665
+ errorCode === 'INIT_ERROR' ? 500 : 500);
666
+ if (errorCode === 'AUTH_FAILED') {
667
+ logger.error('Authentication failed:', details);
668
+ window.dispatchEvent(new CustomEvent('wseAuthFailed', {
669
+ detail: { message: errorMessage, code: errorCode, details }
670
+ }));
671
+ }
672
+ if (errorCode === 'INIT_ERROR' && !recoverable) {
673
+ logger.error('Critical initialization error:', details);
674
+ window.dispatchEvent(new CustomEvent('wseInitializationError', {
675
+ detail: { message: errorMessage, code: errorCode, recoverable: false, details }
676
+ }));
677
+ }
678
+ if (errorCode === 'SERVER_ERROR') {
679
+ logger.error('Server error:', details);
680
+ window.dispatchEvent(new CustomEvent('wseServerError', {
681
+ detail: { message: errorMessage, code: errorCode, details }
682
+ }));
683
+ }
684
+ if (errorCode === 'CIRCUIT_BREAKER_OPEN') {
685
+ logger.error('Circuit breaker activated');
686
+ store.updateCircuitBreaker({
687
+ state: CircuitBreakerState.OPEN,
688
+ lastFailureTime: Date.now()
689
+ });
690
+ }
691
+ if (errorCode === 'RATE_LIMIT_EXCEEDED' || errorMessage.includes('Rate limit')) {
692
+ logger.warn('Rate limit exceeded');
693
+ window.dispatchEvent(new CustomEvent('rateLimitExceeded', {
694
+ detail: {
695
+ message: errorMessage,
696
+ retryAfter: errorData.retry_after || errorData.retryAfter,
697
+ ...details
698
+ }
699
+ }));
700
+ }
701
+ if (errorCode === 'SUBSCRIPTION_FAILED') {
702
+ logger.warn('Subscription failed:', details);
703
+ window.dispatchEvent(new CustomEvent('subscriptionFailed', {
704
+ detail: {
705
+ message: errorMessage,
706
+ topics: errorData.topics || [],
707
+ ...details
708
+ }
709
+ }));
710
+ }
711
+ window.dispatchEvent(new CustomEvent('serverError', {
712
+ detail: {
713
+ message: errorMessage,
714
+ code: errorCode,
715
+ details: errorData,
716
+ recoverable,
717
+ timestamp: errorData.timestamp || new Date().toISOString(),
718
+ severity: errorData.severity || 'error',
719
+ context: {
720
+ messageType: message.t,
721
+ messageId: message.id,
722
+ sequence: message.seq
723
+ }
724
+ }
725
+ }));
726
+ if (!recoverable || errorCode === 'PROTOCOL_ERROR' || errorCode === 'INVALID_MESSAGE') {
727
+ logger.error('Critical error detected, connection may need to be reset');
728
+ window.dispatchEvent(new CustomEvent('criticalError', {
729
+ detail: {
730
+ code: errorCode,
731
+ message: errorMessage,
732
+ shouldReconnect: errorData.shouldReconnect !== false
733
+ }
734
+ }));
735
+ }
736
+ }
737
+ handleHealthCheck(message) {
738
+ const store = useWSEStore.getState();
739
+ store.updateMetrics({ lastHealthCheck: Date.now() });
740
+ this.queueOutgoing({
741
+ t: 'health_check_response',
742
+ p: {
743
+ client_version: WS_CLIENT_VERSION,
744
+ stats: store.metrics,
745
+ diagnostics: store.diagnostics,
746
+ queue_size: useMessageQueueStore.getState().size,
747
+ }
748
+ }, MessagePriority.CRITICAL);
749
+ }
750
+ handleHealthCheckResponse(message) {
751
+ const payload = message.p;
752
+ logger.info('Health check response received:', payload);
753
+ if (payload.diagnostics) {
754
+ const store = useWSEStore.getState();
755
+ store.updateDiagnostics(payload.diagnostics);
756
+ }
757
+ }
758
+ handleRateLimitWarning(message) {
759
+ const warning = message.p;
760
+ logger.warn('Rate limit warning:', warning);
761
+ const store = useWSEStore.getState();
762
+ store.setLastError(`Rate limit: ${warning.message}`, 429);
763
+ if (warning.retry_after) {
764
+ logger.info(`Should retry after ${warning.retry_after} seconds`);
765
+ }
766
+ }
767
+ handleConnectionQuality(message) {
768
+ const payload = message.p;
769
+ logger.info('Connection quality update:', payload);
770
+ const store = useWSEStore.getState();
771
+ if (payload.suggestions && payload.suggestions.length > 0) {
772
+ const currentDiagnostics = store.diagnostics || {
773
+ quality: ConnectionQuality.UNKNOWN,
774
+ stability: 100,
775
+ jitter: 0,
776
+ packetLoss: 0,
777
+ roundTripTime: 0,
778
+ suggestions: [],
779
+ lastAnalysis: null,
780
+ };
781
+ store.updateDiagnostics({
782
+ ...currentDiagnostics,
783
+ suggestions: payload.suggestions,
784
+ quality: payload.quality || currentDiagnostics.quality,
785
+ lastAnalysis: Date.now(),
786
+ });
787
+ }
788
+ if (payload.recommended_settings) {
789
+ const settings = payload.recommended_settings;
790
+ if (settings.compression !== undefined) {
791
+ store.updateConfig({ compressionEnabled: settings.compression });
792
+ }
793
+ if (settings.batch_size !== undefined) {
794
+ this.batchSize = settings.batch_size;
795
+ }
796
+ if (settings.batch_timeout !== undefined) {
797
+ this.batchTimeout = settings.batch_timeout;
798
+ }
799
+ }
800
+ }
801
+ handleSnapshotComplete(message) {
802
+ logger.info('Snapshot complete:', message.p);
803
+ window.dispatchEvent(new CustomEvent('snapshotComplete', {
804
+ detail: message.p
805
+ }));
806
+ }
807
+ handleHeartbeat() {
808
+ const store = useWSEStore.getState();
809
+ store.updateMetrics({ lastHealthCheck: Date.now() });
810
+ }
811
+ handleDebugResponse(message) {
812
+ logger.info('Debug response received:', message.p);
813
+ window.dispatchEvent(new CustomEvent('debugResponse', {
814
+ detail: message.p
815
+ }));
816
+ }
817
+ handleSequenceStatsResponse(message) {
818
+ logger.info('Sequence stats received:', message.p);
819
+ window.dispatchEvent(new CustomEvent('sequenceStatsResponse', {
820
+ detail: message.p
821
+ }));
822
+ }
823
+ handleConfigResponse(message) {
824
+ logger.info('Configuration response:', message.p);
825
+ window.dispatchEvent(new CustomEvent('configResponse', {
826
+ detail: message.p
827
+ }));
828
+ }
829
+ handleConfigUpdateResponse(message) {
830
+ logger.info('Configuration update response:', message.p);
831
+ window.dispatchEvent(new CustomEvent('configUpdateResponse', {
832
+ detail: message.p
833
+ }));
834
+ }
835
+ handleEncryptionResponse(message) {
836
+ logger.info('Encryption response:', message.p);
837
+ const store = useWSEStore.getState();
838
+ if (message.p.enabled !== undefined) {
839
+ store.updateSecurity({
840
+ encryptionEnabled: message.p.enabled,
841
+ encryptionAlgorithm: message.p.algorithms?.encryption || null
842
+ });
843
+ }
844
+ window.dispatchEvent(new CustomEvent('encryptionResponse', {
845
+ detail: message.p
846
+ }));
847
+ }
848
+ handleKeyRotationResponse(message) {
849
+ logger.info('Key rotation response:', message.p);
850
+ const store = useWSEStore.getState();
851
+ if (message.p.success) {
852
+ store.updateSecurity({
853
+ lastKeyRotation: Date.now()
854
+ });
855
+ }
856
+ window.dispatchEvent(new CustomEvent('keyRotationResponse', {
857
+ detail: message.p
858
+ }));
859
+ }
860
+ async handleBatchMessage(message) {
861
+ const payload = message.p;
862
+ logger.info(`Batch message received with ${payload.count} messages`);
863
+ if (payload.messages && Array.isArray(payload.messages)) {
864
+ for (const msg of payload.messages) {
865
+ await this.routeMessage(msg);
866
+ }
867
+ }
868
+ }
869
+ handleBatchMessageResult(message) {
870
+ logger.info('Batch message result:', message.p);
871
+ window.dispatchEvent(new CustomEvent('batchMessageResult', {
872
+ detail: message.p
873
+ }));
874
+ }
875
+ handleMetricsResponse(message) {
876
+ logger.info('Metrics response received:', message.p);
877
+ window.dispatchEvent(new CustomEvent('metricsResponse', {
878
+ detail: message.p
879
+ }));
880
+ }
881
+ handleConnectionStateResponse(message) {
882
+ const payload = message.p;
883
+ logger.info('Connection state response:', payload);
884
+ const store = useWSEStore.getState();
885
+ if (payload.metrics) {
886
+ store.updateMetrics(payload.metrics);
887
+ }
888
+ window.dispatchEvent(new CustomEvent('connectionStateResponse', {
889
+ detail: payload
890
+ }));
891
+ }
892
+ // ---------------------------------------------------------------------------
893
+ // Outgoing Messages with Race Condition Fix
894
+ // ---------------------------------------------------------------------------
895
+ queueOutgoing(message, priority = MessagePriority.NORMAL) {
896
+ if (this.destroyed)
897
+ return false;
898
+ const queueStore = useMessageQueueStore.getState();
899
+ const queuedMessage = {
900
+ id: message.id || crypto.randomUUID(),
901
+ type: message.t || 'unknown',
902
+ payload: message.p || {},
903
+ priority,
904
+ timestamp: Date.now(),
905
+ retries: 0,
906
+ };
907
+ const queued = queueStore.enqueue(queuedMessage);
908
+ if (queued) {
909
+ this.scheduleBatch();
910
+ }
911
+ return queued;
912
+ }
913
+ scheduleBatch() {
914
+ if (this.destroyed || this.batchPromise)
915
+ return;
916
+ this.batchPromise = new Promise((resolve) => {
917
+ const timer = setTimeout(() => {
918
+ if (this.destroyed) {
919
+ resolve();
920
+ return;
921
+ }
922
+ this.processBatchSafe()
923
+ .then(resolve)
924
+ .catch((error) => {
925
+ logger.error('Batch processing error:', error);
926
+ resolve();
927
+ })
928
+ .finally(() => {
929
+ this.batchPromise = null;
930
+ });
931
+ }, this.batchTimeout);
932
+ if (this.batchTimer)
933
+ clearTimeout(this.batchTimer);
934
+ this.batchTimer = timer;
935
+ });
936
+ }
937
+ async processBatchSafe() {
938
+ if (this.processing || this.destroyed)
939
+ return;
940
+ this.processing = true;
941
+ try {
942
+ await this.processBatch();
943
+ }
944
+ finally {
945
+ this.processing = false;
946
+ }
947
+ }
948
+ async processBatch() {
949
+ if (this.destroyed)
950
+ return [];
951
+ const queueStore = useMessageQueueStore.getState();
952
+ queueStore.setProcessing(true);
953
+ try {
954
+ const messages = queueStore.dequeue(this.batchSize);
955
+ if (messages.length === 0) {
956
+ return [];
957
+ }
958
+ messages.sort((a, b) => {
959
+ if (a.priority !== b.priority) {
960
+ return b.priority - a.priority;
961
+ }
962
+ return a.timestamp - b.timestamp;
963
+ });
964
+ const wsMessages = messages.map(msg => ({
965
+ id: msg.id,
966
+ t: msg.type,
967
+ p: msg.payload,
968
+ v: WS_PROTOCOL_VERSION,
969
+ seq: this.sequencer.getNextSequence(),
970
+ ts: new Date().toISOString(),
971
+ pri: msg.priority,
972
+ }));
973
+ const store = useWSEStore.getState();
974
+ store.incrementMetric('messagesSent', wsMessages.length);
975
+ return wsMessages;
976
+ }
977
+ finally {
978
+ queueStore.setProcessing(false);
979
+ if (queueStore.size > 0 && !this.destroyed) {
980
+ this.scheduleBatch();
981
+ }
982
+ }
983
+ }
984
+ // ---------------------------------------------------------------------------
985
+ // Public API
986
+ // ---------------------------------------------------------------------------
987
+ /**
988
+ * Register an event type as high-frequency (uses debug-level logging).
989
+ * High-frequency types are logged at debug level to reduce noise.
990
+ */
991
+ registerHighFrequencyType(type) {
992
+ this.highFrequencyTypes.add(type);
993
+ }
994
+ registerHandler(type, handler) {
995
+ logger.info(`Registering handler for message type: ${type}`);
996
+ this.messageHandlers.set(type, handler);
997
+ }
998
+ unregisterHandler(type) {
999
+ logger.info(`Unregistering handler for message type: ${type}`);
1000
+ this.messageHandlers.delete(type);
1001
+ }
1002
+ clearHandlers() {
1003
+ logger.info('Clearing all message handlers');
1004
+ this.messageHandlers.clear();
1005
+ this.registerDefaultHandlers();
1006
+ }
1007
+ getRegisteredHandlers() {
1008
+ return Array.from(this.messageHandlers.keys());
1009
+ }
1010
+ isHandlerRegistered(type) {
1011
+ return this.messageHandlers.has(type);
1012
+ }
1013
+ waitForHandlers(requiredHandlers, timeout = 5000) {
1014
+ return new Promise((resolve) => {
1015
+ const checkHandlers = () => {
1016
+ const allRegistered = requiredHandlers.every(h => this.isHandlerRegistered(h));
1017
+ if (allRegistered) {
1018
+ resolve(true);
1019
+ return true;
1020
+ }
1021
+ return false;
1022
+ };
1023
+ if (checkHandlers())
1024
+ return;
1025
+ const startTime = Date.now();
1026
+ const interval = setInterval(() => {
1027
+ if (checkHandlers()) {
1028
+ clearInterval(interval);
1029
+ }
1030
+ else if (Date.now() - startTime > timeout) {
1031
+ clearInterval(interval);
1032
+ resolve(requiredHandlers.every(h => this.isHandlerRegistered(h)));
1033
+ }
1034
+ }, 100);
1035
+ });
1036
+ }
1037
+ destroy() {
1038
+ this.destroyed = true;
1039
+ if (this.batchTimer) {
1040
+ clearTimeout(this.batchTimer);
1041
+ this.batchTimer = null;
1042
+ }
1043
+ this.batchPromise = null;
1044
+ this.clearHandlers();
1045
+ this.sequencer.destroy();
1046
+ this.serverReadyProcessed = false;
1047
+ this.serverReadyDetails = null;
1048
+ }
1049
+ }
1050
+ //# sourceMappingURL=MessageProcessor.js.map