create-fluxstack 1.5.5 → 1.7.3

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 (62) hide show
  1. package/README.md +172 -215
  2. package/app/client/src/App.tsx +45 -19
  3. package/app/client/src/components/FluxStackConfig.tsx +1 -1
  4. package/app/client/src/components/HybridLiveCounter.tsx +1 -1
  5. package/app/client/src/components/LiveClock.tsx +1 -1
  6. package/app/client/src/components/MainLayout.tsx +0 -2
  7. package/app/client/src/components/SidebarNavigation.tsx +1 -1
  8. package/app/client/src/components/SystemMonitor.tsx +16 -10
  9. package/app/client/src/components/UserProfile.tsx +1 -1
  10. package/app/server/live/FluxStackConfig.ts +2 -1
  11. package/app/server/live/LiveClockComponent.ts +8 -7
  12. package/app/server/live/SidebarNavigation.ts +2 -1
  13. package/app/server/live/SystemMonitor.ts +1 -0
  14. package/app/server/live/UserProfileComponent.ts +36 -30
  15. package/config/server.config.ts +1 -0
  16. package/core/build/index.ts +14 -0
  17. package/core/cli/command-registry.ts +10 -10
  18. package/core/cli/commands/plugin-deps.ts +13 -5
  19. package/core/cli/plugin-discovery.ts +1 -1
  20. package/core/client/LiveComponentsProvider.tsx +414 -0
  21. package/core/client/hooks/useHybridLiveComponent.ts +194 -530
  22. package/core/client/index.ts +16 -0
  23. package/core/framework/server.ts +144 -63
  24. package/core/index.ts +4 -1
  25. package/core/plugins/built-in/monitoring/index.ts +1 -1
  26. package/core/plugins/built-in/static/index.ts +1 -1
  27. package/core/plugins/built-in/swagger/index.ts +1 -1
  28. package/core/plugins/built-in/vite/index.ts +1 -1
  29. package/core/plugins/config.ts +1 -1
  30. package/core/plugins/discovery.ts +1 -1
  31. package/core/plugins/executor.ts +1 -1
  32. package/core/plugins/index.ts +1 -0
  33. package/core/server/live/ComponentRegistry.ts +3 -1
  34. package/core/server/live/WebSocketConnectionManager.ts +14 -4
  35. package/core/server/live/websocket-plugin.ts +453 -434
  36. package/core/server/middleware/elysia-helpers.ts +3 -5
  37. package/core/server/plugins/database.ts +1 -1
  38. package/core/server/plugins/static-files-plugin.ts +1 -1
  39. package/core/templates/create-project.ts +1 -0
  40. package/core/types/index.ts +1 -1
  41. package/core/types/plugin.ts +1 -1
  42. package/core/types/types.ts +6 -2
  43. package/core/utils/logger/colors.ts +4 -4
  44. package/core/utils/logger/index.ts +37 -4
  45. package/core/utils/logger/winston-logger.ts +1 -1
  46. package/core/utils/sync-version.ts +67 -0
  47. package/core/utils/version.ts +6 -5
  48. package/package.json +3 -2
  49. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +1 -1
  50. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
  51. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +1 -1
  52. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +1 -1
  53. package/vite.config.ts +8 -0
  54. package/.dockerignore +0 -50
  55. package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +0 -475
  56. package/CRYPTO-AUTH-MIDDLEWARES.md +0 -473
  57. package/CRYPTO-AUTH-USAGE.md +0 -491
  58. package/EXEMPLO-ROTA-PROTEGIDA.md +0 -347
  59. package/QUICK-START-CRYPTO-AUTH.md +0 -221
  60. package/app/client/src/components/Teste.tsx +0 -104
  61. package/app/server/live/TesteComponent.ts +0 -87
  62. package/test-crypto-auth.ts +0 -101
@@ -1,435 +1,454 @@
1
- // 🔥 FluxStack Live Components - Enhanced WebSocket Plugin with Connection Management
2
-
3
- import { componentRegistry } from './ComponentRegistry'
4
- import { fileUploadManager } from './FileUploadManager'
5
- import { connectionManager } from './WebSocketConnectionManager'
6
- import { performanceMonitor } from './LiveComponentPerformanceMonitor'
7
- import type { LiveMessage, FileUploadStartMessage, FileUploadChunkMessage, FileUploadCompleteMessage } from '../../types/types'
8
- import type { Plugin, PluginContext } from '../../plugins/types'
9
- import { t } from 'elysia'
10
- import path from 'path'
11
-
12
- export const liveComponentsPlugin: Plugin = {
13
- name: 'live-components',
14
- version: '1.0.0',
15
- description: 'Real-time Live Components with Elysia native WebSocket support',
16
- author: 'FluxStack Team',
17
- priority: 'normal',
18
- category: 'core',
19
- tags: ['websocket', 'real-time', 'live-components'],
20
-
21
- setup: async (context: PluginContext) => {
22
- context.logger.debug('🔌 Setting up Live Components plugin with Elysia WebSocket...')
23
-
24
- // Auto-discover components from app/server/live directory
25
- const componentsPath = path.join(process.cwd(), 'app', 'server', 'live')
26
- await componentRegistry.autoDiscoverComponents(componentsPath)
27
- context.logger.debug('🔍 Component auto-discovery completed')
28
-
29
- // Add WebSocket route for Live Components
30
- context.app
31
- .ws('/api/live/ws', {
32
- body: t.Object({
33
- type: t.String(),
34
- componentId: t.String(),
35
- action: t.Optional(t.String()),
36
- payload: t.Optional(t.Any()),
37
- timestamp: t.Optional(t.Number()),
38
- userId: t.Optional(t.String()),
39
- room: t.Optional(t.String()),
40
- requestId: t.Optional(t.String()),
41
- expectResponse: t.Optional(t.Boolean()),
42
- // File upload specific fields
43
- uploadId: t.Optional(t.String()),
44
- filename: t.Optional(t.String()),
45
- fileType: t.Optional(t.String()),
46
- fileSize: t.Optional(t.Number()),
47
- chunkSize: t.Optional(t.Number()),
48
- chunkIndex: t.Optional(t.Number()),
49
- totalChunks: t.Optional(t.Number()),
50
- data: t.Optional(t.String()),
51
- hash: t.Optional(t.String())
52
- }),
53
-
54
- open(ws) {
55
- const connectionId = `ws-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
56
- console.log(`🔌 Live Components WebSocket connected: ${connectionId}`)
57
-
58
- // Register connection with enhanced connection manager
59
- connectionManager.registerConnection(ws, connectionId, 'live-components')
60
-
61
- // Initialize and store connection data in ws.data
62
- if (!ws.data) {
63
- ws.data = {}
64
- }
65
- ws.data.connectionId = connectionId
66
- ws.data.components = new Map()
67
- ws.data.subscriptions = new Set()
68
- ws.data.connectedAt = new Date()
69
-
70
- // Send connection confirmation
71
- ws.send(JSON.stringify({
72
- type: 'CONNECTION_ESTABLISHED',
73
- connectionId,
74
- timestamp: Date.now(),
75
- features: {
76
- compression: true,
77
- encryption: true,
78
- offlineQueue: true,
79
- loadBalancing: true
80
- }
81
- }))
82
- },
83
-
84
- async message(ws, message: LiveMessage) {
85
- try {
86
- // Add connection metadata
87
- message.timestamp = Date.now()
88
-
89
- console.log(`📨 Received message:`, {
90
- type: message.type,
91
- componentId: message.componentId,
92
- action: message.action,
93
- requestId: message.requestId
94
- })
95
-
96
- // Handle different message types
97
- switch (message.type) {
98
- case 'COMPONENT_MOUNT':
99
- await handleComponentMount(ws, message)
100
- break
101
- case 'COMPONENT_REHYDRATE':
102
- await handleComponentRehydrate(ws, message)
103
- break
104
- case 'COMPONENT_UNMOUNT':
105
- await handleComponentUnmount(ws, message)
106
- break
107
- case 'CALL_ACTION':
108
- await handleActionCall(ws, message)
109
- break
110
- case 'PROPERTY_UPDATE':
111
- await handlePropertyUpdate(ws, message)
112
- break
113
- case 'FILE_UPLOAD_START':
114
- await handleFileUploadStart(ws, message as FileUploadStartMessage)
115
- break
116
- case 'FILE_UPLOAD_CHUNK':
117
- await handleFileUploadChunk(ws, message as FileUploadChunkMessage)
118
- break
119
- case 'FILE_UPLOAD_COMPLETE':
120
- await handleFileUploadComplete(ws, message as FileUploadCompleteMessage)
121
- break
122
- default:
123
- console.warn(`❌ Unknown message type: ${message.type}`)
124
- ws.send(JSON.stringify({
125
- type: 'ERROR',
126
- error: `Unknown message type: ${message.type}`,
127
- timestamp: Date.now()
128
- }))
129
- }
130
- } catch (error) {
131
- console.error('❌ WebSocket message error:', error)
132
- ws.send(JSON.stringify({
133
- type: 'ERROR',
134
- error: error instanceof Error ? error.message : 'Unknown error',
135
- timestamp: Date.now()
136
- }))
137
- }
138
- },
139
-
140
- close(ws) {
141
- const connectionId = ws.data?.connectionId
142
- console.log(`🔌 Live Components WebSocket disconnected: ${connectionId}`)
143
-
144
- // Cleanup connection in connection manager
145
- if (connectionId) {
146
- connectionManager.cleanupConnection(connectionId)
147
- }
148
-
149
- // Cleanup components for this connection
150
- componentRegistry.cleanupConnection(ws)
151
- }
152
- })
153
-
154
- // Add Live Components info routes
155
- .get('/api/live/websocket-info', () => {
156
- return {
157
- success: true,
158
- message: 'Live Components WebSocket available via Elysia',
159
- endpoint: 'ws://localhost:3000/api/live/ws',
160
- status: 'running',
161
- connectionManager: connectionManager.getSystemStats()
162
- }
163
- })
164
- .get('/api/live/stats', () => {
165
- const stats = componentRegistry.getStats()
166
- return {
167
- success: true,
168
- stats,
169
- timestamp: new Date().toISOString()
170
- }
171
- })
172
- .get('/api/live/health', () => {
173
- return {
174
- success: true,
175
- service: 'FluxStack Live Components',
176
- status: 'operational',
177
- components: componentRegistry.getStats().components,
178
- connections: connectionManager.getSystemStats(),
179
- uptime: process.uptime(),
180
- timestamp: new Date().toISOString()
181
- }
182
- })
183
- .get('/api/live/connections', () => {
184
- return {
185
- success: true,
186
- connections: connectionManager.getAllConnectionMetrics(),
187
- systemStats: connectionManager.getSystemStats(),
188
- timestamp: new Date().toISOString()
189
- }
190
- })
191
- .get('/api/live/connections/:connectionId', ({ params }) => {
192
- const metrics = connectionManager.getConnectionMetrics(params.connectionId)
193
- if (!metrics) {
194
- return {
195
- success: false,
196
- error: 'Connection not found'
197
- }
198
- }
199
- return {
200
- success: true,
201
- connection: metrics,
202
- timestamp: new Date().toISOString()
203
- }
204
- })
205
- .get('/api/live/pools/:poolId/stats', ({ params }) => {
206
- const stats = connectionManager.getPoolStats(params.poolId)
207
- if (!stats) {
208
- return {
209
- success: false,
210
- error: 'Pool not found'
211
- }
212
- }
213
- return {
214
- success: true,
215
- pool: params.poolId,
216
- stats,
217
- timestamp: new Date().toISOString()
218
- }
219
- })
220
- .get('/api/live/performance/dashboard', () => {
221
- return {
222
- success: true,
223
- dashboard: performanceMonitor.generateDashboard(),
224
- timestamp: new Date().toISOString()
225
- }
226
- })
227
- .get('/api/live/performance/components/:componentId', ({ params }) => {
228
- const metrics = performanceMonitor.getComponentMetrics(params.componentId)
229
- if (!metrics) {
230
- return {
231
- success: false,
232
- error: 'Component metrics not found'
233
- }
234
- }
235
-
236
- const alerts = performanceMonitor.getComponentAlerts(params.componentId)
237
- const suggestions = performanceMonitor.getComponentSuggestions(params.componentId)
238
-
239
- return {
240
- success: true,
241
- component: params.componentId,
242
- metrics,
243
- alerts,
244
- suggestions,
245
- timestamp: new Date().toISOString()
246
- }
247
- })
248
- .post('/api/live/performance/alerts/:alertId/resolve', ({ params }) => {
249
- const resolved = performanceMonitor.resolveAlert(params.alertId)
250
- return {
251
- success: resolved,
252
- message: resolved ? 'Alert resolved' : 'Alert not found',
253
- timestamp: new Date().toISOString()
254
- }
255
- })
256
- },
257
-
258
- onServerStart: async (context: PluginContext) => {
259
- context.logger.debug('🔌 Live Components WebSocket ready on /api/live/ws')
260
- }
261
- }
262
-
263
- // Handler functions for WebSocket messages
264
- async function handleComponentMount(ws: any, message: LiveMessage) {
265
- const result = await componentRegistry.handleMessage(ws, message)
266
-
267
- if (result !== null) {
268
- const response = {
269
- type: 'COMPONENT_MOUNTED',
270
- componentId: message.componentId,
271
- success: result.success,
272
- result: result.result,
273
- error: result.error,
274
- requestId: message.requestId,
275
- timestamp: Date.now()
276
- }
277
- ws.send(JSON.stringify(response))
278
- }
279
- }
280
-
281
- async function handleComponentRehydrate(ws: any, message: LiveMessage) {
282
- console.log('🔄 Processing component re-hydration request:', {
283
- componentId: message.componentId,
284
- payload: message.payload
285
- })
286
-
287
- try {
288
- const { componentName, signedState, room, userId } = message.payload || {}
289
-
290
- if (!componentName || !signedState) {
291
- throw new Error('Missing componentName or signedState in rehydration payload')
292
- }
293
-
294
- const result = await componentRegistry.rehydrateComponent(
295
- message.componentId,
296
- componentName,
297
- signedState,
298
- ws,
299
- { room, userId }
300
- )
301
-
302
- const response = {
303
- type: 'COMPONENT_REHYDRATED',
304
- componentId: message.componentId,
305
- success: result.success,
306
- result: {
307
- newComponentId: result.newComponentId,
308
- ...result
309
- },
310
- error: result.error,
311
- requestId: message.requestId,
312
- timestamp: Date.now()
313
- }
314
-
315
- console.log('📤 Sending COMPONENT_REHYDRATED response:', {
316
- type: response.type,
317
- success: response.success,
318
- newComponentId: response.result?.newComponentId,
319
- requestId: response.requestId
320
- })
321
-
322
- ws.send(JSON.stringify(response))
323
-
324
- } catch (error: any) {
325
- console.error('❌ Re-hydration handler error:', error.message)
326
-
327
- const errorResponse = {
328
- type: 'COMPONENT_REHYDRATED',
329
- componentId: message.componentId,
330
- success: false,
331
- error: error.message,
332
- requestId: message.requestId,
333
- timestamp: Date.now()
334
- }
335
-
336
- ws.send(JSON.stringify(errorResponse))
337
- }
338
- }
339
-
340
- async function handleComponentUnmount(ws: any, message: LiveMessage) {
341
- const result = await componentRegistry.handleMessage(ws, message)
342
-
343
- if (result !== null) {
344
- const response = {
345
- type: 'COMPONENT_UNMOUNTED',
346
- componentId: message.componentId,
347
- success: result.success,
348
- requestId: message.requestId,
349
- timestamp: Date.now()
350
- }
351
- ws.send(JSON.stringify(response))
352
- }
353
- }
354
-
355
- async function handleActionCall(ws: any, message: LiveMessage) {
356
- const result = await componentRegistry.handleMessage(ws, message)
357
-
358
- if (result !== null) {
359
- const response = {
360
- type: message.expectResponse ? 'ACTION_RESPONSE' : 'MESSAGE_RESPONSE',
361
- originalType: message.type,
362
- componentId: message.componentId,
363
- success: result.success,
364
- result: result.result,
365
- error: result.error,
366
- requestId: message.requestId,
367
- timestamp: Date.now()
368
- }
369
- ws.send(JSON.stringify(response))
370
- }
371
- }
372
-
373
- async function handlePropertyUpdate(ws: any, message: LiveMessage) {
374
- const result = await componentRegistry.handleMessage(ws, message)
375
-
376
- if (result !== null) {
377
- const response = {
378
- type: 'PROPERTY_UPDATED',
379
- componentId: message.componentId,
380
- success: result.success,
381
- result: result.result,
382
- error: result.error,
383
- requestId: message.requestId,
384
- timestamp: Date.now()
385
- }
386
- ws.send(JSON.stringify(response))
387
- }
388
- }
389
-
390
- // File Upload Handler Functions
391
- async function handleFileUploadStart(ws: any, message: FileUploadStartMessage) {
392
- console.log('📤 Starting file upload:', message.uploadId)
393
-
394
- const result = await fileUploadManager.startUpload(message)
395
-
396
- const response = {
397
- type: 'FILE_UPLOAD_START_RESPONSE',
398
- componentId: message.componentId,
399
- uploadId: message.uploadId,
400
- success: result.success,
401
- error: result.error,
402
- requestId: message.requestId,
403
- timestamp: Date.now()
404
- }
405
-
406
- ws.send(JSON.stringify(response))
407
- }
408
-
409
- async function handleFileUploadChunk(ws: any, message: FileUploadChunkMessage) {
410
- console.log(`📦 Receiving chunk ${message.chunkIndex + 1} for upload ${message.uploadId}`)
411
-
412
- const progressResponse = await fileUploadManager.receiveChunk(message, ws)
413
-
414
- if (progressResponse) {
415
- ws.send(JSON.stringify(progressResponse))
416
- } else {
417
- // Send error response
418
- const errorResponse = {
419
- type: 'FILE_UPLOAD_ERROR',
420
- componentId: message.componentId,
421
- uploadId: message.uploadId,
422
- error: 'Failed to process chunk',
423
- requestId: message.requestId,
424
- timestamp: Date.now()
425
- }
426
- ws.send(JSON.stringify(errorResponse))
427
- }
428
- }
429
-
430
- async function handleFileUploadComplete(ws: any, message: FileUploadCompleteMessage) {
431
- console.log('✅ Completing file upload:', message.uploadId)
432
-
433
- const completeResponse = await fileUploadManager.completeUpload(message)
434
- ws.send(JSON.stringify(completeResponse))
1
+ // 🔥 FluxStack Live Components - Enhanced WebSocket Plugin with Connection Management
2
+
3
+ import { componentRegistry } from './ComponentRegistry'
4
+ import { fileUploadManager } from './FileUploadManager'
5
+ import { connectionManager } from './WebSocketConnectionManager'
6
+ import { performanceMonitor } from './LiveComponentPerformanceMonitor'
7
+ import type { LiveMessage, FileUploadStartMessage, FileUploadChunkMessage, FileUploadCompleteMessage } from '../../types/types'
8
+ import type { Plugin, PluginContext } from '@/core/index'
9
+ import { t } from 'elysia'
10
+ import path from 'path'
11
+
12
+ export const liveComponentsPlugin: Plugin = {
13
+ name: 'live-components',
14
+ version: '1.0.0',
15
+ description: 'Real-time Live Components with Elysia native WebSocket support',
16
+ author: 'FluxStack Team',
17
+ priority: 'normal',
18
+ category: 'core',
19
+ tags: ['websocket', 'real-time', 'live-components'],
20
+
21
+ setup: async (context: PluginContext) => {
22
+ context.logger.debug('🔌 Setting up Live Components plugin with Elysia WebSocket...')
23
+
24
+ // Auto-discover components from app/server/live directory
25
+ const componentsPath = path.join(process.cwd(), 'app', 'server', 'live')
26
+ await componentRegistry.autoDiscoverComponents(componentsPath)
27
+ context.logger.debug('🔍 Component auto-discovery completed')
28
+
29
+ // Add WebSocket route for Live Components
30
+ context.app
31
+ .ws('/api/live/ws', {
32
+ body: t.Object({
33
+ type: t.String(),
34
+ componentId: t.String(),
35
+ action: t.Optional(t.String()),
36
+ payload: t.Optional(t.Any()),
37
+ timestamp: t.Optional(t.Number()),
38
+ userId: t.Optional(t.String()),
39
+ room: t.Optional(t.String()),
40
+ requestId: t.Optional(t.String()),
41
+ expectResponse: t.Optional(t.Boolean()),
42
+ // File upload specific fields
43
+ uploadId: t.Optional(t.String()),
44
+ filename: t.Optional(t.String()),
45
+ fileType: t.Optional(t.String()),
46
+ fileSize: t.Optional(t.Number()),
47
+ chunkSize: t.Optional(t.Number()),
48
+ chunkIndex: t.Optional(t.Number()),
49
+ totalChunks: t.Optional(t.Number()),
50
+ data: t.Optional(t.String()),
51
+ hash: t.Optional(t.String())
52
+ }),
53
+
54
+ open(ws) {
55
+ const connectionId = `ws-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
56
+ console.log(`🔌 Live Components WebSocket connected: ${connectionId}`)
57
+
58
+ // Register connection with enhanced connection manager
59
+ connectionManager.registerConnection(ws, connectionId, 'live-components')
60
+
61
+ // Initialize and store connection data in ws.data
62
+ if (!ws.data) {
63
+ ws.data = {}
64
+ }
65
+ ws.data.connectionId = connectionId
66
+ ws.data.components = new Map()
67
+ ws.data.subscriptions = new Set()
68
+ ws.data.connectedAt = new Date()
69
+
70
+ // Send connection confirmation
71
+ ws.send(JSON.stringify({
72
+ type: 'CONNECTION_ESTABLISHED',
73
+ connectionId,
74
+ timestamp: Date.now(),
75
+ features: {
76
+ compression: true,
77
+ encryption: true,
78
+ offlineQueue: true,
79
+ loadBalancing: true
80
+ }
81
+ }))
82
+ },
83
+
84
+ async message(ws, message: LiveMessage) {
85
+ try {
86
+ // Add connection metadata
87
+ message.timestamp = Date.now()
88
+
89
+ console.log(`📨 Received message:`, {
90
+ type: message.type,
91
+ componentId: message.componentId,
92
+ action: message.action,
93
+ requestId: message.requestId
94
+ })
95
+
96
+ // Handle different message types
97
+ switch (message.type) {
98
+ case 'COMPONENT_MOUNT':
99
+ await handleComponentMount(ws, message)
100
+ break
101
+ case 'COMPONENT_REHYDRATE':
102
+ await handleComponentRehydrate(ws, message)
103
+ break
104
+ case 'COMPONENT_UNMOUNT':
105
+ await handleComponentUnmount(ws, message)
106
+ break
107
+ case 'CALL_ACTION':
108
+ await handleActionCall(ws, message)
109
+ break
110
+ case 'PROPERTY_UPDATE':
111
+ await handlePropertyUpdate(ws, message)
112
+ break
113
+ case 'COMPONENT_PING':
114
+ await handleComponentPing(ws, message)
115
+ break
116
+ case 'FILE_UPLOAD_START':
117
+ await handleFileUploadStart(ws, message as FileUploadStartMessage)
118
+ break
119
+ case 'FILE_UPLOAD_CHUNK':
120
+ await handleFileUploadChunk(ws, message as FileUploadChunkMessage)
121
+ break
122
+ case 'FILE_UPLOAD_COMPLETE':
123
+ await handleFileUploadComplete(ws, message as FileUploadCompleteMessage)
124
+ break
125
+ default:
126
+ console.warn(`❌ Unknown message type: ${message.type}`)
127
+ ws.send(JSON.stringify({
128
+ type: 'ERROR',
129
+ error: `Unknown message type: ${message.type}`,
130
+ timestamp: Date.now()
131
+ }))
132
+ }
133
+ } catch (error) {
134
+ console.error('❌ WebSocket message error:', error)
135
+ ws.send(JSON.stringify({
136
+ type: 'ERROR',
137
+ error: error instanceof Error ? error.message : 'Unknown error',
138
+ timestamp: Date.now()
139
+ }))
140
+ }
141
+ },
142
+
143
+ close(ws) {
144
+ const connectionId = ws.data?.connectionId
145
+ console.log(`🔌 Live Components WebSocket disconnected: ${connectionId}`)
146
+
147
+ // Cleanup connection in connection manager
148
+ if (connectionId) {
149
+ connectionManager.cleanupConnection(connectionId)
150
+ }
151
+
152
+ // Cleanup components for this connection
153
+ componentRegistry.cleanupConnection(ws)
154
+ }
155
+ })
156
+
157
+ // Add Live Components info routes
158
+ .get('/api/live/websocket-info', () => {
159
+ return {
160
+ success: true,
161
+ message: 'Live Components WebSocket available via Elysia',
162
+ endpoint: 'ws://localhost:3000/api/live/ws',
163
+ status: 'running',
164
+ connectionManager: connectionManager.getSystemStats()
165
+ }
166
+ })
167
+ .get('/api/live/stats', () => {
168
+ const stats = componentRegistry.getStats()
169
+ return {
170
+ success: true,
171
+ stats,
172
+ timestamp: new Date().toISOString()
173
+ }
174
+ })
175
+ .get('/api/live/health', () => {
176
+ return {
177
+ success: true,
178
+ service: 'FluxStack Live Components',
179
+ status: 'operational',
180
+ components: componentRegistry.getStats().components,
181
+ connections: connectionManager.getSystemStats(),
182
+ uptime: process.uptime(),
183
+ timestamp: new Date().toISOString()
184
+ }
185
+ })
186
+ .get('/api/live/connections', () => {
187
+ return {
188
+ success: true,
189
+ connections: connectionManager.getAllConnectionMetrics(),
190
+ systemStats: connectionManager.getSystemStats(),
191
+ timestamp: new Date().toISOString()
192
+ }
193
+ })
194
+ .get('/api/live/connections/:connectionId', ({ params }) => {
195
+ const metrics = connectionManager.getConnectionMetrics(params.connectionId)
196
+ if (!metrics) {
197
+ return {
198
+ success: false,
199
+ error: 'Connection not found'
200
+ }
201
+ }
202
+ return {
203
+ success: true,
204
+ connection: metrics,
205
+ timestamp: new Date().toISOString()
206
+ }
207
+ })
208
+ .get('/api/live/pools/:poolId/stats', ({ params }) => {
209
+ const stats = connectionManager.getPoolStats(params.poolId)
210
+ if (!stats) {
211
+ return {
212
+ success: false,
213
+ error: 'Pool not found'
214
+ }
215
+ }
216
+ return {
217
+ success: true,
218
+ pool: params.poolId,
219
+ stats,
220
+ timestamp: new Date().toISOString()
221
+ }
222
+ })
223
+ .get('/api/live/performance/dashboard', () => {
224
+ return {
225
+ success: true,
226
+ dashboard: performanceMonitor.generateDashboard(),
227
+ timestamp: new Date().toISOString()
228
+ }
229
+ })
230
+ .get('/api/live/performance/components/:componentId', ({ params }) => {
231
+ const metrics = performanceMonitor.getComponentMetrics(params.componentId)
232
+ if (!metrics) {
233
+ return {
234
+ success: false,
235
+ error: 'Component metrics not found'
236
+ }
237
+ }
238
+
239
+ const alerts = performanceMonitor.getComponentAlerts(params.componentId)
240
+ const suggestions = performanceMonitor.getComponentSuggestions(params.componentId)
241
+
242
+ return {
243
+ success: true,
244
+ component: params.componentId,
245
+ metrics,
246
+ alerts,
247
+ suggestions,
248
+ timestamp: new Date().toISOString()
249
+ }
250
+ })
251
+ .post('/api/live/performance/alerts/:alertId/resolve', ({ params }) => {
252
+ const resolved = performanceMonitor.resolveAlert(params.alertId)
253
+ return {
254
+ success: resolved,
255
+ message: resolved ? 'Alert resolved' : 'Alert not found',
256
+ timestamp: new Date().toISOString()
257
+ }
258
+ })
259
+ },
260
+
261
+ onServerStart: async (context: PluginContext) => {
262
+ context.logger.debug('🔌 Live Components WebSocket ready on /api/live/ws')
263
+ }
264
+ }
265
+
266
+ // Handler functions for WebSocket messages
267
+ async function handleComponentMount(ws: any, message: LiveMessage) {
268
+ const result = await componentRegistry.handleMessage(ws, message)
269
+
270
+ if (result !== null) {
271
+ const response = {
272
+ type: 'COMPONENT_MOUNTED',
273
+ componentId: message.componentId,
274
+ success: result.success,
275
+ result: result.result,
276
+ error: result.error,
277
+ requestId: message.requestId,
278
+ timestamp: Date.now()
279
+ }
280
+ ws.send(JSON.stringify(response))
281
+ }
282
+ }
283
+
284
+ async function handleComponentRehydrate(ws: any, message: LiveMessage) {
285
+ console.log('🔄 Processing component re-hydration request:', {
286
+ componentId: message.componentId,
287
+ payload: message.payload
288
+ })
289
+
290
+ try {
291
+ const { componentName, signedState, room, userId } = message.payload || {}
292
+
293
+ if (!componentName || !signedState) {
294
+ throw new Error('Missing componentName or signedState in rehydration payload')
295
+ }
296
+
297
+ const result = await componentRegistry.rehydrateComponent(
298
+ message.componentId,
299
+ componentName,
300
+ signedState,
301
+ ws,
302
+ { room, userId }
303
+ )
304
+
305
+ const response = {
306
+ type: 'COMPONENT_REHYDRATED',
307
+ componentId: message.componentId,
308
+ success: result.success,
309
+ result: {
310
+ newComponentId: result.newComponentId,
311
+ ...result
312
+ },
313
+ error: result.error,
314
+ requestId: message.requestId,
315
+ timestamp: Date.now()
316
+ }
317
+
318
+ console.log('📤 Sending COMPONENT_REHYDRATED response:', {
319
+ type: response.type,
320
+ success: response.success,
321
+ newComponentId: response.result?.newComponentId,
322
+ requestId: response.requestId
323
+ })
324
+
325
+ ws.send(JSON.stringify(response))
326
+
327
+ } catch (error: any) {
328
+ console.error('❌ Re-hydration handler error:', error.message)
329
+
330
+ const errorResponse = {
331
+ type: 'COMPONENT_REHYDRATED',
332
+ componentId: message.componentId,
333
+ success: false,
334
+ error: error.message,
335
+ requestId: message.requestId,
336
+ timestamp: Date.now()
337
+ }
338
+
339
+ ws.send(JSON.stringify(errorResponse))
340
+ }
341
+ }
342
+
343
+ async function handleComponentUnmount(ws: any, message: LiveMessage) {
344
+ const result = await componentRegistry.handleMessage(ws, message)
345
+
346
+ if (result !== null) {
347
+ const response = {
348
+ type: 'COMPONENT_UNMOUNTED',
349
+ componentId: message.componentId,
350
+ success: result.success,
351
+ requestId: message.requestId,
352
+ timestamp: Date.now()
353
+ }
354
+ ws.send(JSON.stringify(response))
355
+ }
356
+ }
357
+
358
+ async function handleActionCall(ws: any, message: LiveMessage) {
359
+ const result = await componentRegistry.handleMessage(ws, message)
360
+
361
+ if (result !== null) {
362
+ const response = {
363
+ type: message.expectResponse ? 'ACTION_RESPONSE' : 'MESSAGE_RESPONSE',
364
+ originalType: message.type,
365
+ componentId: message.componentId,
366
+ success: result.success,
367
+ result: result.result,
368
+ error: result.error,
369
+ requestId: message.requestId,
370
+ timestamp: Date.now()
371
+ }
372
+ ws.send(JSON.stringify(response))
373
+ }
374
+ }
375
+
376
+ async function handlePropertyUpdate(ws: any, message: LiveMessage) {
377
+ const result = await componentRegistry.handleMessage(ws, message)
378
+
379
+ if (result !== null) {
380
+ const response = {
381
+ type: 'PROPERTY_UPDATED',
382
+ componentId: message.componentId,
383
+ success: result.success,
384
+ result: result.result,
385
+ error: result.error,
386
+ requestId: message.requestId,
387
+ timestamp: Date.now()
388
+ }
389
+ ws.send(JSON.stringify(response))
390
+ }
391
+ }
392
+
393
+ async function handleComponentPing(ws: any, message: LiveMessage) {
394
+ // Update component's last activity timestamp
395
+ const updated = componentRegistry.updateComponentActivity(message.componentId)
396
+
397
+ // Send pong response
398
+ const response = {
399
+ type: 'COMPONENT_PONG',
400
+ componentId: message.componentId,
401
+ success: updated,
402
+ requestId: message.requestId,
403
+ timestamp: Date.now()
404
+ }
405
+
406
+ ws.send(JSON.stringify(response))
407
+ }
408
+
409
+ // File Upload Handler Functions
410
+ async function handleFileUploadStart(ws: any, message: FileUploadStartMessage) {
411
+ console.log('📤 Starting file upload:', message.uploadId)
412
+
413
+ const result = await fileUploadManager.startUpload(message)
414
+
415
+ const response = {
416
+ type: 'FILE_UPLOAD_START_RESPONSE',
417
+ componentId: message.componentId,
418
+ uploadId: message.uploadId,
419
+ success: result.success,
420
+ error: result.error,
421
+ requestId: message.requestId,
422
+ timestamp: Date.now()
423
+ }
424
+
425
+ ws.send(JSON.stringify(response))
426
+ }
427
+
428
+ async function handleFileUploadChunk(ws: any, message: FileUploadChunkMessage) {
429
+ console.log(`📦 Receiving chunk ${message.chunkIndex + 1} for upload ${message.uploadId}`)
430
+
431
+ const progressResponse = await fileUploadManager.receiveChunk(message, ws)
432
+
433
+ if (progressResponse) {
434
+ ws.send(JSON.stringify(progressResponse))
435
+ } else {
436
+ // Send error response
437
+ const errorResponse = {
438
+ type: 'FILE_UPLOAD_ERROR',
439
+ componentId: message.componentId,
440
+ uploadId: message.uploadId,
441
+ error: 'Failed to process chunk',
442
+ requestId: message.requestId,
443
+ timestamp: Date.now()
444
+ }
445
+ ws.send(JSON.stringify(errorResponse))
446
+ }
447
+ }
448
+
449
+ async function handleFileUploadComplete(ws: any, message: FileUploadCompleteMessage) {
450
+ console.log('✅ Completing file upload:', message.uploadId)
451
+
452
+ const completeResponse = await fileUploadManager.completeUpload(message)
453
+ ws.send(JSON.stringify(completeResponse))
435
454
  }