create-fluxstack 1.13.0 → 1.15.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/LLMD/patterns/anti-patterns.md +100 -0
- package/LLMD/reference/routing.md +39 -39
- package/LLMD/resources/live-auth.md +20 -2
- package/LLMD/resources/live-components.md +300 -21
- package/LLMD/resources/live-logging.md +95 -33
- package/LLMD/resources/live-upload.md +59 -8
- package/app/client/.live-stubs/LiveAdminPanel.js +5 -0
- package/app/client/.live-stubs/LiveChat.js +7 -0
- package/app/client/.live-stubs/LiveCounter.js +9 -0
- package/app/client/.live-stubs/LiveForm.js +11 -0
- package/app/client/.live-stubs/LiveLocalCounter.js +8 -0
- package/app/client/.live-stubs/LiveRoomChat.js +10 -0
- package/app/client/.live-stubs/LiveTodoList.js +9 -0
- package/app/client/.live-stubs/LiveUpload.js +15 -0
- package/app/client/index.html +2 -2
- package/app/client/public/favicon.svg +46 -0
- package/app/client/src/App.tsx +13 -1
- package/app/client/src/assets/fluxstack-static.svg +46 -0
- package/app/client/src/assets/fluxstack.svg +183 -0
- package/app/client/src/components/AppLayout.tsx +146 -9
- package/app/client/src/components/BackButton.tsx +13 -13
- package/app/client/src/components/DemoPage.tsx +4 -4
- package/app/client/src/live/AuthDemo.tsx +23 -21
- package/app/client/src/live/ChatDemo.tsx +2 -2
- package/app/client/src/live/CounterDemo.tsx +12 -12
- package/app/client/src/live/FormDemo.tsx +2 -2
- package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
- package/app/client/src/live/RoomChatDemo.tsx +24 -16
- package/app/client/src/live/TodoListDemo.tsx +158 -0
- package/app/client/src/main.tsx +13 -13
- package/app/client/src/pages/ApiTestPage.tsx +6 -6
- package/app/client/src/pages/HomePage.tsx +80 -52
- package/app/server/auth/DevAuthProvider.ts +2 -2
- package/app/server/auth/JWTAuthProvider.example.ts +2 -2
- package/app/server/index.ts +2 -2
- package/app/server/live/LiveAdminPanel.ts +2 -1
- package/app/server/live/LiveChat.ts +78 -77
- package/app/server/live/LiveCounter.ts +1 -1
- package/app/server/live/LiveForm.ts +1 -0
- package/app/server/live/LiveLocalCounter.ts +38 -37
- package/app/server/live/LiveProtectedChat.ts +2 -1
- package/app/server/live/LiveRoomChat.ts +1 -0
- package/app/server/live/LiveTodoList.ts +110 -0
- package/app/server/live/LiveUpload.ts +1 -0
- package/app/server/live/register-components.ts +19 -19
- package/app/server/routes/room.routes.ts +1 -2
- package/config/system/runtime.config.ts +4 -0
- package/core/build/live-components-generator.ts +1 -1
- package/core/build/optimizer.ts +235 -235
- package/core/build/vite-plugins.ts +28 -0
- package/core/client/components/LiveDebugger.tsx +1324 -0
- package/core/client/hooks/useLiveUpload.ts +3 -4
- package/core/client/index.ts +41 -21
- package/core/framework/server.ts +1 -1
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +4 -0
- package/core/plugins/built-in/vite/index.ts +75 -21
- package/core/server/index.ts +14 -15
- package/core/server/live/auto-generated-components.ts +6 -3
- package/core/server/live/index.ts +95 -21
- package/core/server/live/websocket-plugin.ts +27 -862
- package/core/server/plugins/static-files-plugin.ts +179 -69
- package/core/types/build.ts +219 -219
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +77 -890
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +1 -1
- package/package.json +5 -1
- package/plugins/crypto-auth/index.ts +1 -1
- package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +2 -2
- package/vite.config.ts +40 -12
- package/app/client/src/assets/react.svg +0 -1
- package/core/client/LiveComponentsProvider.tsx +0 -531
- package/core/client/components/Live.tsx +0 -105
- package/core/client/hooks/AdaptiveChunkSizer.ts +0 -215
- package/core/client/hooks/state-validator.ts +0 -130
- package/core/client/hooks/useChunkedUpload.ts +0 -359
- package/core/client/hooks/useLiveChunkedUpload.ts +0 -87
- package/core/client/hooks/useLiveComponent.ts +0 -843
- package/core/client/hooks/useRoom.ts +0 -409
- package/core/client/hooks/useRoomProxy.ts +0 -382
- package/core/server/live/ComponentRegistry.ts +0 -1099
- package/core/server/live/FileUploadManager.ts +0 -282
- package/core/server/live/LiveComponentPerformanceMonitor.ts +0 -931
- package/core/server/live/LiveLogger.ts +0 -111
- package/core/server/live/LiveRoomManager.ts +0 -262
- package/core/server/live/RoomEventBus.ts +0 -234
- package/core/server/live/RoomStateManager.ts +0 -172
- package/core/server/live/SingleConnectionManager.ts +0 -0
- package/core/server/live/StateSignature.ts +0 -645
- package/core/server/live/WebSocketConnectionManager.ts +0 -709
- package/core/server/live/auth/LiveAuthContext.ts +0 -71
- package/core/server/live/auth/LiveAuthManager.ts +0 -304
- package/core/server/live/auth/index.ts +0 -19
- package/core/server/live/auth/types.ts +0 -179
|
@@ -1,883 +1,48 @@
|
|
|
1
|
-
//
|
|
1
|
+
// FluxStack Live Components Plugin — delegates to @fluxstack/live
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { liveRoomManager, type RoomMessage } from './LiveRoomManager'
|
|
8
|
-
import { liveAuthManager } from './auth/LiveAuthManager'
|
|
9
|
-
import { ANONYMOUS_CONTEXT } from './auth/LiveAuthContext'
|
|
10
|
-
import type { LiveMessage, FileUploadStartMessage, FileUploadChunkMessage, FileUploadCompleteMessage, BinaryChunkHeader, FluxStackWebSocket, FluxStackWSData } from '@core/types/types'
|
|
11
|
-
import type { Plugin, PluginContext } from '@core/index'
|
|
12
|
-
import { t, Elysia } from 'elysia'
|
|
3
|
+
import { LiveServer } from '@fluxstack/live'
|
|
4
|
+
import type { LiveAuthProvider } from '@fluxstack/live'
|
|
5
|
+
import { ElysiaTransport } from '@fluxstack/live-elysia'
|
|
6
|
+
import type { Plugin, PluginContext } from '@core/plugins/types'
|
|
13
7
|
import path from 'path'
|
|
14
|
-
import { liveLog } from './LiveLogger'
|
|
15
8
|
|
|
16
|
-
//
|
|
9
|
+
// Expose the LiveServer instance so other parts of FluxStack can access it
|
|
10
|
+
export let liveServer: LiveServer | null = null
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
message: t.String(),
|
|
21
|
-
endpoint: t.String(),
|
|
22
|
-
status: t.String(),
|
|
23
|
-
connectionManager: t.Any()
|
|
24
|
-
}, {
|
|
25
|
-
description: 'WebSocket connection information and system statistics'
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
const LiveStatsSchema = t.Object({
|
|
29
|
-
success: t.Boolean(),
|
|
30
|
-
stats: t.Any(),
|
|
31
|
-
timestamp: t.String()
|
|
32
|
-
}, {
|
|
33
|
-
description: 'Live Components statistics including registered components and instances'
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const LiveHealthSchema = t.Object({
|
|
37
|
-
success: t.Boolean(),
|
|
38
|
-
service: t.String(),
|
|
39
|
-
status: t.String(),
|
|
40
|
-
components: t.Number(),
|
|
41
|
-
connections: t.Any(),
|
|
42
|
-
uptime: t.Number(),
|
|
43
|
-
timestamp: t.String()
|
|
44
|
-
}, {
|
|
45
|
-
description: 'Health status of Live Components service'
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const LiveConnectionsSchema = t.Object({
|
|
49
|
-
success: t.Boolean(),
|
|
50
|
-
connections: t.Array(t.Any()),
|
|
51
|
-
systemStats: t.Any(),
|
|
52
|
-
timestamp: t.String()
|
|
53
|
-
}, {
|
|
54
|
-
description: 'List of all active WebSocket connections with metrics'
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const LiveConnectionDetailsSchema = t.Union([
|
|
58
|
-
t.Object({
|
|
59
|
-
success: t.Literal(true),
|
|
60
|
-
connection: t.Any(),
|
|
61
|
-
timestamp: t.String()
|
|
62
|
-
}),
|
|
63
|
-
t.Object({
|
|
64
|
-
success: t.Literal(false),
|
|
65
|
-
error: t.String()
|
|
66
|
-
})
|
|
67
|
-
], {
|
|
68
|
-
description: 'Detailed metrics for a specific connection'
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
const LivePoolStatsSchema = t.Union([
|
|
72
|
-
t.Object({
|
|
73
|
-
success: t.Literal(true),
|
|
74
|
-
pool: t.String(),
|
|
75
|
-
stats: t.Any(),
|
|
76
|
-
timestamp: t.String()
|
|
77
|
-
}),
|
|
78
|
-
t.Object({
|
|
79
|
-
success: t.Literal(false),
|
|
80
|
-
error: t.String()
|
|
81
|
-
})
|
|
82
|
-
], {
|
|
83
|
-
description: 'Statistics for a specific connection pool'
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const LivePerformanceDashboardSchema = t.Object({
|
|
87
|
-
success: t.Boolean(),
|
|
88
|
-
dashboard: t.Any(),
|
|
89
|
-
timestamp: t.String()
|
|
90
|
-
}, {
|
|
91
|
-
description: 'Performance monitoring dashboard data'
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
const LiveComponentMetricsSchema = t.Union([
|
|
95
|
-
t.Object({
|
|
96
|
-
success: t.Literal(true),
|
|
97
|
-
component: t.String(),
|
|
98
|
-
metrics: t.Any(),
|
|
99
|
-
alerts: t.Array(t.Any()),
|
|
100
|
-
suggestions: t.Array(t.Any()),
|
|
101
|
-
timestamp: t.String()
|
|
102
|
-
}),
|
|
103
|
-
t.Object({
|
|
104
|
-
success: t.Literal(false),
|
|
105
|
-
error: t.String()
|
|
106
|
-
})
|
|
107
|
-
], {
|
|
108
|
-
description: 'Performance metrics, alerts and suggestions for a specific component'
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
const LiveAlertResolveSchema = t.Object({
|
|
112
|
-
success: t.Boolean(),
|
|
113
|
-
message: t.String(),
|
|
114
|
-
timestamp: t.String()
|
|
115
|
-
}, {
|
|
116
|
-
description: 'Result of alert resolution operation'
|
|
117
|
-
})
|
|
12
|
+
// Queue for auth providers registered before LiveServer is created
|
|
13
|
+
export const pendingAuthProviders: LiveAuthProvider[] = []
|
|
118
14
|
|
|
119
15
|
export const liveComponentsPlugin: Plugin = {
|
|
120
16
|
name: 'live-components',
|
|
121
|
-
version: '
|
|
122
|
-
description: 'Real-time Live Components
|
|
17
|
+
version: '2.0.0',
|
|
18
|
+
description: 'Real-time Live Components powered by @fluxstack/live',
|
|
123
19
|
author: 'FluxStack Team',
|
|
124
20
|
priority: 'normal',
|
|
125
21
|
category: 'core',
|
|
126
22
|
tags: ['websocket', 'real-time', 'live-components'],
|
|
127
|
-
|
|
23
|
+
|
|
128
24
|
setup: async (context: PluginContext) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// Auto-discover components from app/server/live directory
|
|
25
|
+
const transport = new ElysiaTransport(context.app)
|
|
132
26
|
const componentsPath = path.join(process.cwd(), 'app', 'server', 'live')
|
|
133
|
-
await componentRegistry.autoDiscoverComponents(componentsPath)
|
|
134
|
-
context.logger.debug('🔍 Component auto-discovery completed')
|
|
135
|
-
|
|
136
|
-
// Create grouped routes for Live Components with documentation
|
|
137
|
-
const liveRoutes = new Elysia({ prefix: '/api/live', tags: ['Live Components'] })
|
|
138
|
-
// WebSocket route - supports both JSON and binary messages
|
|
139
|
-
.ws('/ws', {
|
|
140
|
-
// Use t.Any() to allow both JSON objects and binary data
|
|
141
|
-
// Binary messages will be ArrayBuffer/Uint8Array, JSON will be parsed objects
|
|
142
|
-
body: t.Any(),
|
|
143
|
-
|
|
144
|
-
async open(ws) {
|
|
145
|
-
const socket = ws as unknown as FluxStackWebSocket
|
|
146
|
-
const connectionId = `ws-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
147
|
-
liveLog('websocket', null, `🔌 Live Components WebSocket connected: ${connectionId}`)
|
|
148
|
-
|
|
149
|
-
// Register connection with enhanced connection manager
|
|
150
|
-
connectionManager.registerConnection(ws as unknown as FluxStackWebSocket, connectionId, 'live-components')
|
|
151
|
-
|
|
152
|
-
// Initialize and store connection data in ws.data
|
|
153
|
-
const wsData: FluxStackWSData = {
|
|
154
|
-
connectionId,
|
|
155
|
-
components: new Map(),
|
|
156
|
-
subscriptions: new Set(),
|
|
157
|
-
connectedAt: new Date()
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Assign data to websocket (Elysia creates ws.data from context)
|
|
161
|
-
if (!socket.data) {
|
|
162
|
-
(socket as { data: FluxStackWSData }).data = wsData
|
|
163
|
-
} else {
|
|
164
|
-
socket.data.connectionId = connectionId
|
|
165
|
-
socket.data.components = new Map()
|
|
166
|
-
socket.data.subscriptions = new Set()
|
|
167
|
-
socket.data.connectedAt = new Date()
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 🔒 Try to authenticate from query params (token=xxx)
|
|
171
|
-
try {
|
|
172
|
-
const query = (ws as any).data?.query as Record<string, string> | undefined
|
|
173
|
-
const token = query?.token
|
|
174
|
-
if (token && liveAuthManager.hasProviders()) {
|
|
175
|
-
const authContext = await liveAuthManager.authenticate({ token })
|
|
176
|
-
socket.data.authContext = authContext
|
|
177
|
-
if (authContext.authenticated) {
|
|
178
|
-
socket.data.userId = authContext.user?.id
|
|
179
|
-
liveLog('websocket', null, `🔒 WebSocket authenticated via query: user=${authContext.user?.id}`)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
} catch {
|
|
183
|
-
// Query param auth is optional - continue without auth
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Send connection confirmation
|
|
187
|
-
ws.send(JSON.stringify({
|
|
188
|
-
type: 'CONNECTION_ESTABLISHED',
|
|
189
|
-
connectionId,
|
|
190
|
-
timestamp: Date.now(),
|
|
191
|
-
authenticated: socket.data.authContext?.authenticated ?? false,
|
|
192
|
-
userId: socket.data.authContext?.user?.id,
|
|
193
|
-
features: {
|
|
194
|
-
compression: true,
|
|
195
|
-
encryption: true,
|
|
196
|
-
offlineQueue: true,
|
|
197
|
-
loadBalancing: true,
|
|
198
|
-
auth: liveAuthManager.hasProviders()
|
|
199
|
-
}
|
|
200
|
-
}))
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
async message(ws: unknown, rawMessage: LiveMessage | ArrayBuffer | Uint8Array) {
|
|
204
|
-
const socket = ws as FluxStackWebSocket
|
|
205
|
-
try {
|
|
206
|
-
let message: LiveMessage
|
|
207
|
-
let binaryChunkData: Buffer | null = null
|
|
208
|
-
|
|
209
|
-
// Check if this is a binary message (file upload chunk)
|
|
210
|
-
if (rawMessage instanceof ArrayBuffer || rawMessage instanceof Uint8Array) {
|
|
211
|
-
// Binary protocol: [4 bytes header length][JSON header][binary data]
|
|
212
|
-
const buffer = rawMessage instanceof ArrayBuffer
|
|
213
|
-
? Buffer.from(rawMessage)
|
|
214
|
-
: Buffer.from(rawMessage.buffer, rawMessage.byteOffset, rawMessage.byteLength)
|
|
215
|
-
|
|
216
|
-
// Read header length (first 4 bytes, little-endian)
|
|
217
|
-
const headerLength = buffer.readUInt32LE(0)
|
|
218
|
-
|
|
219
|
-
// Extract and parse JSON header
|
|
220
|
-
const headerJson = buffer.slice(4, 4 + headerLength).toString('utf-8')
|
|
221
|
-
const header = JSON.parse(headerJson) as BinaryChunkHeader
|
|
222
|
-
|
|
223
|
-
// Extract binary chunk data
|
|
224
|
-
binaryChunkData = buffer.slice(4 + headerLength)
|
|
225
|
-
|
|
226
|
-
liveLog('messages', null, `📦 Binary chunk received: ${binaryChunkData.length} bytes for upload ${header.uploadId}`)
|
|
227
|
-
|
|
228
|
-
// Create message with binary data attached
|
|
229
|
-
message = {
|
|
230
|
-
...header,
|
|
231
|
-
data: binaryChunkData, // Buffer instead of base64 string
|
|
232
|
-
timestamp: Date.now()
|
|
233
|
-
} as unknown as LiveMessage
|
|
234
|
-
} else {
|
|
235
|
-
// Regular JSON message
|
|
236
|
-
message = rawMessage as LiveMessage
|
|
237
|
-
message.timestamp = Date.now()
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
liveLog('messages', message.componentId || null, `📨 Received message:`, {
|
|
241
|
-
type: message.type,
|
|
242
|
-
componentId: message.componentId,
|
|
243
|
-
action: message.action,
|
|
244
|
-
requestId: message.requestId,
|
|
245
|
-
isBinary: binaryChunkData !== null
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// Handle different message types
|
|
249
|
-
switch (message.type) {
|
|
250
|
-
case 'COMPONENT_MOUNT':
|
|
251
|
-
await handleComponentMount(socket, message)
|
|
252
|
-
break
|
|
253
|
-
case 'COMPONENT_REHYDRATE':
|
|
254
|
-
await handleComponentRehydrate(socket, message)
|
|
255
|
-
break
|
|
256
|
-
case 'COMPONENT_UNMOUNT':
|
|
257
|
-
await handleComponentUnmount(socket, message)
|
|
258
|
-
break
|
|
259
|
-
case 'CALL_ACTION':
|
|
260
|
-
await handleActionCall(socket, message)
|
|
261
|
-
break
|
|
262
|
-
case 'PROPERTY_UPDATE':
|
|
263
|
-
await handlePropertyUpdate(socket, message)
|
|
264
|
-
break
|
|
265
|
-
case 'COMPONENT_PING':
|
|
266
|
-
await handleComponentPing(socket, message)
|
|
267
|
-
break
|
|
268
|
-
case 'AUTH':
|
|
269
|
-
await handleAuth(socket, message)
|
|
270
|
-
break
|
|
271
|
-
case 'FILE_UPLOAD_START':
|
|
272
|
-
await handleFileUploadStart(socket, message as FileUploadStartMessage)
|
|
273
|
-
break
|
|
274
|
-
case 'FILE_UPLOAD_CHUNK':
|
|
275
|
-
await handleFileUploadChunk(socket, message as FileUploadChunkMessage, binaryChunkData)
|
|
276
|
-
break
|
|
277
|
-
case 'FILE_UPLOAD_COMPLETE':
|
|
278
|
-
await handleFileUploadComplete(socket, message as unknown as FileUploadCompleteMessage)
|
|
279
|
-
break
|
|
280
|
-
// Room system messages
|
|
281
|
-
case 'ROOM_JOIN':
|
|
282
|
-
await handleRoomJoin(socket, message as unknown as RoomMessage)
|
|
283
|
-
break
|
|
284
|
-
case 'ROOM_LEAVE':
|
|
285
|
-
await handleRoomLeave(socket, message as unknown as RoomMessage)
|
|
286
|
-
break
|
|
287
|
-
case 'ROOM_EMIT':
|
|
288
|
-
await handleRoomEmit(socket, message as unknown as RoomMessage)
|
|
289
|
-
break
|
|
290
|
-
case 'ROOM_STATE_SET':
|
|
291
|
-
await handleRoomStateSet(socket, message as unknown as RoomMessage)
|
|
292
|
-
break
|
|
293
|
-
default:
|
|
294
|
-
console.warn(`❌ Unknown message type: ${message.type}`)
|
|
295
|
-
socket.send(JSON.stringify({
|
|
296
|
-
type: 'ERROR',
|
|
297
|
-
error: `Unknown message type: ${message.type}`,
|
|
298
|
-
timestamp: Date.now()
|
|
299
|
-
}))
|
|
300
|
-
}
|
|
301
|
-
} catch (error) {
|
|
302
|
-
console.error('❌ WebSocket message error:', error)
|
|
303
|
-
socket.send(JSON.stringify({
|
|
304
|
-
type: 'ERROR',
|
|
305
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
306
|
-
timestamp: Date.now()
|
|
307
|
-
}))
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
|
|
311
|
-
close(ws) {
|
|
312
|
-
const socket = ws as unknown as FluxStackWebSocket
|
|
313
|
-
const connectionId = socket.data?.connectionId
|
|
314
|
-
liveLog('websocket', null, `🔌 Live Components WebSocket disconnected: ${connectionId}`)
|
|
315
|
-
|
|
316
|
-
// Cleanup connection in connection manager
|
|
317
|
-
if (connectionId) {
|
|
318
|
-
connectionManager.cleanupConnection(connectionId)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Cleanup components for this connection
|
|
322
|
-
componentRegistry.cleanupConnection(socket)
|
|
323
|
-
}
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
// ===== Live Components Information Routes =====
|
|
327
|
-
.get('/websocket-info', () => {
|
|
328
|
-
return {
|
|
329
|
-
success: true,
|
|
330
|
-
message: 'Live Components WebSocket available via Elysia',
|
|
331
|
-
endpoint: 'ws://localhost:3000/api/live/ws',
|
|
332
|
-
status: 'running',
|
|
333
|
-
connectionManager: connectionManager.getSystemStats()
|
|
334
|
-
}
|
|
335
|
-
}, {
|
|
336
|
-
detail: {
|
|
337
|
-
summary: 'Get WebSocket Information',
|
|
338
|
-
description: 'Returns WebSocket endpoint information and connection manager statistics',
|
|
339
|
-
tags: ['Live Components', 'WebSocket']
|
|
340
|
-
},
|
|
341
|
-
response: LiveWebSocketInfoSchema
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
.get('/stats', () => {
|
|
345
|
-
const stats = componentRegistry.getStats()
|
|
346
|
-
return {
|
|
347
|
-
success: true,
|
|
348
|
-
stats,
|
|
349
|
-
timestamp: new Date().toISOString()
|
|
350
|
-
}
|
|
351
|
-
}, {
|
|
352
|
-
detail: {
|
|
353
|
-
summary: 'Get Live Components Statistics',
|
|
354
|
-
description: 'Returns statistics about registered components and active instances',
|
|
355
|
-
tags: ['Live Components', 'Monitoring']
|
|
356
|
-
},
|
|
357
|
-
response: LiveStatsSchema
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
.get('/health', () => {
|
|
361
|
-
return {
|
|
362
|
-
success: true,
|
|
363
|
-
service: 'FluxStack Live Components',
|
|
364
|
-
status: 'operational',
|
|
365
|
-
components: componentRegistry.getStats().components,
|
|
366
|
-
connections: connectionManager.getSystemStats(),
|
|
367
|
-
uptime: process.uptime(),
|
|
368
|
-
timestamp: new Date().toISOString()
|
|
369
|
-
}
|
|
370
|
-
}, {
|
|
371
|
-
detail: {
|
|
372
|
-
summary: 'Health Check',
|
|
373
|
-
description: 'Returns the health status of the Live Components service',
|
|
374
|
-
tags: ['Live Components', 'Health']
|
|
375
|
-
},
|
|
376
|
-
response: LiveHealthSchema
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
// ===== Connection Management Routes =====
|
|
380
|
-
.get('/connections', () => {
|
|
381
|
-
return {
|
|
382
|
-
success: true,
|
|
383
|
-
connections: connectionManager.getAllConnectionMetrics(),
|
|
384
|
-
systemStats: connectionManager.getSystemStats(),
|
|
385
|
-
timestamp: new Date().toISOString()
|
|
386
|
-
}
|
|
387
|
-
}, {
|
|
388
|
-
detail: {
|
|
389
|
-
summary: 'List All Connections',
|
|
390
|
-
description: 'Returns all active WebSocket connections with their metrics',
|
|
391
|
-
tags: ['Live Components', 'Connections']
|
|
392
|
-
},
|
|
393
|
-
response: LiveConnectionsSchema
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
.get('/connections/:connectionId', ({ params }) => {
|
|
397
|
-
const metrics = connectionManager.getConnectionMetrics(params.connectionId)
|
|
398
|
-
if (!metrics) {
|
|
399
|
-
return {
|
|
400
|
-
success: false,
|
|
401
|
-
error: 'Connection not found'
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
return {
|
|
405
|
-
success: true,
|
|
406
|
-
connection: metrics,
|
|
407
|
-
timestamp: new Date().toISOString()
|
|
408
|
-
}
|
|
409
|
-
}, {
|
|
410
|
-
detail: {
|
|
411
|
-
summary: 'Get Connection Details',
|
|
412
|
-
description: 'Returns detailed metrics for a specific WebSocket connection',
|
|
413
|
-
tags: ['Live Components', 'Connections']
|
|
414
|
-
},
|
|
415
|
-
params: t.Object({
|
|
416
|
-
connectionId: t.String({ description: 'The unique connection identifier' })
|
|
417
|
-
}),
|
|
418
|
-
response: LiveConnectionDetailsSchema
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
.get('/pools/:poolId/stats', ({ params }) => {
|
|
422
|
-
const stats = connectionManager.getPoolStats(params.poolId)
|
|
423
|
-
if (!stats) {
|
|
424
|
-
return {
|
|
425
|
-
success: false,
|
|
426
|
-
error: 'Pool not found'
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
return {
|
|
430
|
-
success: true,
|
|
431
|
-
pool: params.poolId,
|
|
432
|
-
stats,
|
|
433
|
-
timestamp: new Date().toISOString()
|
|
434
|
-
}
|
|
435
|
-
}, {
|
|
436
|
-
detail: {
|
|
437
|
-
summary: 'Get Pool Statistics',
|
|
438
|
-
description: 'Returns statistics for a specific connection pool',
|
|
439
|
-
tags: ['Live Components', 'Connections', 'Pools']
|
|
440
|
-
},
|
|
441
|
-
params: t.Object({
|
|
442
|
-
poolId: t.String({ description: 'The unique pool identifier' })
|
|
443
|
-
}),
|
|
444
|
-
response: LivePoolStatsSchema
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
// ===== Performance Monitoring Routes =====
|
|
448
|
-
.get('/performance/dashboard', () => {
|
|
449
|
-
return {
|
|
450
|
-
success: true,
|
|
451
|
-
dashboard: performanceMonitor.generateDashboard(),
|
|
452
|
-
timestamp: new Date().toISOString()
|
|
453
|
-
}
|
|
454
|
-
}, {
|
|
455
|
-
detail: {
|
|
456
|
-
summary: 'Performance Dashboard',
|
|
457
|
-
description: 'Returns comprehensive performance monitoring dashboard data',
|
|
458
|
-
tags: ['Live Components', 'Performance']
|
|
459
|
-
},
|
|
460
|
-
response: LivePerformanceDashboardSchema
|
|
461
|
-
})
|
|
462
27
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
error: 'Component metrics not found'
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const alerts = performanceMonitor.getComponentAlerts(params.componentId)
|
|
473
|
-
const suggestions = performanceMonitor.getComponentSuggestions(params.componentId)
|
|
474
|
-
|
|
475
|
-
return {
|
|
476
|
-
success: true,
|
|
477
|
-
component: params.componentId,
|
|
478
|
-
metrics,
|
|
479
|
-
alerts,
|
|
480
|
-
suggestions,
|
|
481
|
-
timestamp: new Date().toISOString()
|
|
482
|
-
}
|
|
483
|
-
}, {
|
|
484
|
-
detail: {
|
|
485
|
-
summary: 'Get Component Performance Metrics',
|
|
486
|
-
description: 'Returns performance metrics, alerts, and optimization suggestions for a specific component',
|
|
487
|
-
tags: ['Live Components', 'Performance']
|
|
488
|
-
},
|
|
489
|
-
params: t.Object({
|
|
490
|
-
componentId: t.String({ description: 'The unique component identifier' })
|
|
491
|
-
}),
|
|
492
|
-
response: LiveComponentMetricsSchema
|
|
493
|
-
})
|
|
494
|
-
|
|
495
|
-
.post('/performance/alerts/:alertId/resolve', ({ params }) => {
|
|
496
|
-
const resolved = performanceMonitor.resolveAlert(params.alertId)
|
|
497
|
-
return {
|
|
498
|
-
success: resolved,
|
|
499
|
-
message: resolved ? 'Alert resolved' : 'Alert not found',
|
|
500
|
-
timestamp: new Date().toISOString()
|
|
501
|
-
}
|
|
502
|
-
}, {
|
|
503
|
-
detail: {
|
|
504
|
-
summary: 'Resolve Performance Alert',
|
|
505
|
-
description: 'Marks a performance alert as resolved',
|
|
506
|
-
tags: ['Live Components', 'Performance', 'Alerts']
|
|
507
|
-
},
|
|
508
|
-
params: t.Object({
|
|
509
|
-
alertId: t.String({ description: 'The unique alert identifier' })
|
|
510
|
-
}),
|
|
511
|
-
response: LiveAlertResolveSchema
|
|
512
|
-
})
|
|
513
|
-
|
|
514
|
-
// Register the grouped routes with the main app
|
|
515
|
-
context.app.use(liveRoutes)
|
|
516
|
-
},
|
|
517
|
-
|
|
518
|
-
onServerStart: async (context: PluginContext) => {
|
|
519
|
-
context.logger.debug('🔌 Live Components WebSocket ready on /api/live/ws')
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Handler functions for WebSocket messages
|
|
524
|
-
async function handleComponentMount(ws: FluxStackWebSocket, message: LiveMessage) {
|
|
525
|
-
const result = await componentRegistry.handleMessage(ws, message)
|
|
526
|
-
|
|
527
|
-
if (result !== null) {
|
|
528
|
-
const response = {
|
|
529
|
-
type: 'COMPONENT_MOUNTED',
|
|
530
|
-
componentId: message.componentId,
|
|
531
|
-
success: result.success,
|
|
532
|
-
result: result.result,
|
|
533
|
-
error: result.error,
|
|
534
|
-
requestId: message.requestId,
|
|
535
|
-
timestamp: Date.now()
|
|
536
|
-
}
|
|
537
|
-
ws.send(JSON.stringify(response))
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
async function handleComponentRehydrate(ws: FluxStackWebSocket, message: LiveMessage) {
|
|
542
|
-
liveLog('lifecycle', message.componentId, '🔄 Processing component re-hydration request:', {
|
|
543
|
-
componentId: message.componentId,
|
|
544
|
-
payload: message.payload
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
try {
|
|
548
|
-
const { componentName, signedState, room, userId } = message.payload || {}
|
|
549
|
-
|
|
550
|
-
if (!componentName || !signedState) {
|
|
551
|
-
throw new Error('Missing componentName or signedState in rehydration payload')
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const result = await componentRegistry.rehydrateComponent(
|
|
555
|
-
message.componentId,
|
|
556
|
-
componentName,
|
|
557
|
-
signedState,
|
|
558
|
-
ws,
|
|
559
|
-
{ room, userId }
|
|
560
|
-
)
|
|
561
|
-
|
|
562
|
-
const response = {
|
|
563
|
-
type: 'COMPONENT_REHYDRATED',
|
|
564
|
-
componentId: message.componentId,
|
|
565
|
-
success: result.success,
|
|
566
|
-
result: {
|
|
567
|
-
newComponentId: result.newComponentId,
|
|
568
|
-
...result
|
|
569
|
-
},
|
|
570
|
-
error: result.error,
|
|
571
|
-
requestId: message.requestId,
|
|
572
|
-
timestamp: Date.now()
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
liveLog('lifecycle', message.componentId, '📤 Sending COMPONENT_REHYDRATED response:', {
|
|
576
|
-
type: response.type,
|
|
577
|
-
success: response.success,
|
|
578
|
-
newComponentId: response.result?.newComponentId,
|
|
579
|
-
requestId: response.requestId
|
|
28
|
+
liveServer = new LiveServer({
|
|
29
|
+
transport,
|
|
30
|
+
componentsPath,
|
|
31
|
+
wsPath: '/api/live/ws',
|
|
32
|
+
httpPrefix: '/api/live',
|
|
580
33
|
})
|
|
581
|
-
|
|
582
|
-
ws.send(JSON.stringify(response))
|
|
583
|
-
|
|
584
|
-
} catch (error: any) {
|
|
585
|
-
console.error('❌ Re-hydration handler error:', error.message)
|
|
586
|
-
|
|
587
|
-
const errorResponse = {
|
|
588
|
-
type: 'COMPONENT_REHYDRATED',
|
|
589
|
-
componentId: message.componentId,
|
|
590
|
-
success: false,
|
|
591
|
-
error: error.message,
|
|
592
|
-
requestId: message.requestId,
|
|
593
|
-
timestamp: Date.now()
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
ws.send(JSON.stringify(errorResponse))
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
async function handleComponentUnmount(ws: FluxStackWebSocket, message: LiveMessage) {
|
|
601
|
-
const result = await componentRegistry.handleMessage(ws, message)
|
|
602
|
-
|
|
603
|
-
if (result !== null) {
|
|
604
|
-
const response = {
|
|
605
|
-
type: 'COMPONENT_UNMOUNTED',
|
|
606
|
-
componentId: message.componentId,
|
|
607
|
-
success: result.success,
|
|
608
|
-
requestId: message.requestId,
|
|
609
|
-
timestamp: Date.now()
|
|
610
|
-
}
|
|
611
|
-
ws.send(JSON.stringify(response))
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
async function handleActionCall(ws: FluxStackWebSocket, message: LiveMessage) {
|
|
616
|
-
const result = await componentRegistry.handleMessage(ws, message)
|
|
617
|
-
|
|
618
|
-
if (result !== null) {
|
|
619
|
-
const response = {
|
|
620
|
-
type: message.expectResponse ? 'ACTION_RESPONSE' : 'MESSAGE_RESPONSE',
|
|
621
|
-
originalType: message.type,
|
|
622
|
-
componentId: message.componentId,
|
|
623
|
-
success: result.success,
|
|
624
|
-
result: result.result,
|
|
625
|
-
error: result.error,
|
|
626
|
-
requestId: message.requestId,
|
|
627
|
-
timestamp: Date.now()
|
|
628
|
-
}
|
|
629
|
-
ws.send(JSON.stringify(response))
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
async function handlePropertyUpdate(ws: FluxStackWebSocket, message: LiveMessage) {
|
|
634
|
-
const result = await componentRegistry.handleMessage(ws, message)
|
|
635
|
-
|
|
636
|
-
if (result !== null) {
|
|
637
|
-
const response = {
|
|
638
|
-
type: 'PROPERTY_UPDATED',
|
|
639
|
-
componentId: message.componentId,
|
|
640
|
-
success: result.success,
|
|
641
|
-
result: result.result,
|
|
642
|
-
error: result.error,
|
|
643
|
-
requestId: message.requestId,
|
|
644
|
-
timestamp: Date.now()
|
|
645
|
-
}
|
|
646
|
-
ws.send(JSON.stringify(response))
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
async function handleComponentPing(ws: FluxStackWebSocket, message: LiveMessage) {
|
|
651
|
-
// Update component's last activity timestamp
|
|
652
|
-
const updated = componentRegistry.updateComponentActivity(message.componentId)
|
|
653
|
-
|
|
654
|
-
// Send pong response
|
|
655
|
-
const response = {
|
|
656
|
-
type: 'COMPONENT_PONG',
|
|
657
|
-
componentId: message.componentId,
|
|
658
|
-
success: updated,
|
|
659
|
-
requestId: message.requestId,
|
|
660
|
-
timestamp: Date.now()
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
ws.send(JSON.stringify(response))
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// ===== Auth Handler =====
|
|
667
|
-
|
|
668
|
-
async function handleAuth(ws: FluxStackWebSocket, message: LiveMessage) {
|
|
669
|
-
liveLog('websocket', null, '🔒 Processing WebSocket authentication request')
|
|
670
|
-
|
|
671
|
-
try {
|
|
672
|
-
const credentials = message.payload || {}
|
|
673
|
-
const providerName = credentials.provider as string | undefined
|
|
674
34
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
success: false,
|
|
679
|
-
error: 'No auth providers configured',
|
|
680
|
-
requestId: message.requestId,
|
|
681
|
-
timestamp: Date.now()
|
|
682
|
-
}))
|
|
683
|
-
return
|
|
35
|
+
// Replay any auth providers that were registered before setup()
|
|
36
|
+
for (const provider of pendingAuthProviders) {
|
|
37
|
+
liveServer.useAuth(provider)
|
|
684
38
|
}
|
|
39
|
+
pendingAuthProviders.length = 0
|
|
685
40
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
ws.data.authContext = authContext
|
|
690
|
-
|
|
691
|
-
if (authContext.authenticated) {
|
|
692
|
-
ws.data.userId = authContext.user?.id
|
|
693
|
-
liveLog('websocket', null, `🔒 WebSocket authenticated: user=${authContext.user?.id}`)
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
ws.send(JSON.stringify({
|
|
697
|
-
type: 'AUTH_RESPONSE',
|
|
698
|
-
success: authContext.authenticated,
|
|
699
|
-
authenticated: authContext.authenticated,
|
|
700
|
-
userId: authContext.user?.id,
|
|
701
|
-
roles: authContext.user?.roles,
|
|
702
|
-
requestId: message.requestId,
|
|
703
|
-
timestamp: Date.now()
|
|
704
|
-
}))
|
|
705
|
-
} catch (error: any) {
|
|
706
|
-
console.error('🔒 WebSocket auth error:', error.message)
|
|
707
|
-
ws.send(JSON.stringify({
|
|
708
|
-
type: 'AUTH_RESPONSE',
|
|
709
|
-
success: false,
|
|
710
|
-
error: error.message,
|
|
711
|
-
requestId: message.requestId,
|
|
712
|
-
timestamp: Date.now()
|
|
713
|
-
}))
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// File Upload Handler Functions
|
|
718
|
-
async function handleFileUploadStart(ws: FluxStackWebSocket, message: FileUploadStartMessage) {
|
|
719
|
-
liveLog('messages', message.componentId || null, '📤 Starting file upload:', message.uploadId)
|
|
720
|
-
|
|
721
|
-
const result = await fileUploadManager.startUpload(message)
|
|
722
|
-
|
|
723
|
-
const response = {
|
|
724
|
-
type: 'FILE_UPLOAD_START_RESPONSE',
|
|
725
|
-
componentId: message.componentId,
|
|
726
|
-
uploadId: message.uploadId,
|
|
727
|
-
success: result.success,
|
|
728
|
-
error: result.error,
|
|
729
|
-
requestId: message.requestId,
|
|
730
|
-
timestamp: Date.now()
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
ws.send(JSON.stringify(response))
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
async function handleFileUploadChunk(ws: FluxStackWebSocket, message: FileUploadChunkMessage, binaryData: Buffer | null = null) {
|
|
737
|
-
liveLog('messages', message.componentId || null, `📦 Receiving chunk ${message.chunkIndex + 1} for upload ${message.uploadId}${binaryData ? ' (binary)' : ' (base64)'}`)
|
|
738
|
-
|
|
739
|
-
const progressResponse = await fileUploadManager.receiveChunk(message, ws, binaryData)
|
|
740
|
-
|
|
741
|
-
if (progressResponse) {
|
|
742
|
-
// Add requestId to response so client can correlate it
|
|
743
|
-
const responseWithRequestId = {
|
|
744
|
-
...progressResponse,
|
|
745
|
-
requestId: message.requestId,
|
|
746
|
-
success: true
|
|
747
|
-
}
|
|
748
|
-
ws.send(JSON.stringify(responseWithRequestId))
|
|
749
|
-
} else {
|
|
750
|
-
// Send error response
|
|
751
|
-
const errorResponse = {
|
|
752
|
-
type: 'FILE_UPLOAD_ERROR',
|
|
753
|
-
componentId: message.componentId,
|
|
754
|
-
uploadId: message.uploadId,
|
|
755
|
-
error: 'Failed to process chunk',
|
|
756
|
-
requestId: message.requestId,
|
|
757
|
-
success: false,
|
|
758
|
-
timestamp: Date.now()
|
|
759
|
-
}
|
|
760
|
-
ws.send(JSON.stringify(errorResponse))
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
async function handleFileUploadComplete(ws: FluxStackWebSocket, message: FileUploadCompleteMessage) {
|
|
765
|
-
liveLog('messages', null, '✅ Completing file upload:', message.uploadId)
|
|
766
|
-
|
|
767
|
-
const completeResponse = await fileUploadManager.completeUpload(message)
|
|
768
|
-
|
|
769
|
-
// Add requestId to response so client can correlate it
|
|
770
|
-
const responseWithRequestId = {
|
|
771
|
-
...completeResponse,
|
|
772
|
-
requestId: message.requestId
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
ws.send(JSON.stringify(responseWithRequestId))
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// ===== Room System Handlers =====
|
|
779
|
-
|
|
780
|
-
async function handleRoomJoin(ws: FluxStackWebSocket, message: RoomMessage) {
|
|
781
|
-
liveLog('rooms', message.componentId, `🚪 Component ${message.componentId} joining room ${message.roomId}`)
|
|
782
|
-
|
|
783
|
-
try {
|
|
784
|
-
const result = liveRoomManager.joinRoom(
|
|
785
|
-
message.componentId,
|
|
786
|
-
message.roomId,
|
|
787
|
-
ws,
|
|
788
|
-
message.data?.initialState
|
|
789
|
-
)
|
|
790
|
-
|
|
791
|
-
const response = {
|
|
792
|
-
type: 'ROOM_JOINED',
|
|
793
|
-
componentId: message.componentId,
|
|
794
|
-
roomId: message.roomId,
|
|
795
|
-
success: true,
|
|
796
|
-
state: result.state,
|
|
797
|
-
requestId: message.requestId,
|
|
798
|
-
timestamp: Date.now()
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
ws.send(JSON.stringify(response))
|
|
802
|
-
} catch (error: any) {
|
|
803
|
-
ws.send(JSON.stringify({
|
|
804
|
-
type: 'ERROR',
|
|
805
|
-
componentId: message.componentId,
|
|
806
|
-
roomId: message.roomId,
|
|
807
|
-
error: error.message,
|
|
808
|
-
requestId: message.requestId,
|
|
809
|
-
timestamp: Date.now()
|
|
810
|
-
}))
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
async function handleRoomLeave(ws: FluxStackWebSocket, message: RoomMessage) {
|
|
815
|
-
liveLog('rooms', message.componentId, `🚶 Component ${message.componentId} leaving room ${message.roomId}`)
|
|
816
|
-
|
|
817
|
-
try {
|
|
818
|
-
liveRoomManager.leaveRoom(message.componentId, message.roomId)
|
|
819
|
-
|
|
820
|
-
const response = {
|
|
821
|
-
type: 'ROOM_LEFT',
|
|
822
|
-
componentId: message.componentId,
|
|
823
|
-
roomId: message.roomId,
|
|
824
|
-
success: true,
|
|
825
|
-
requestId: message.requestId,
|
|
826
|
-
timestamp: Date.now()
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
ws.send(JSON.stringify(response))
|
|
830
|
-
} catch (error: any) {
|
|
831
|
-
ws.send(JSON.stringify({
|
|
832
|
-
type: 'ERROR',
|
|
833
|
-
componentId: message.componentId,
|
|
834
|
-
roomId: message.roomId,
|
|
835
|
-
error: error.message,
|
|
836
|
-
requestId: message.requestId,
|
|
837
|
-
timestamp: Date.now()
|
|
838
|
-
}))
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
async function handleRoomEmit(ws: FluxStackWebSocket, message: RoomMessage) {
|
|
843
|
-
liveLog('rooms', message.componentId, `📡 Component ${message.componentId} emitting '${message.event}' to room ${message.roomId}`)
|
|
844
|
-
|
|
845
|
-
try {
|
|
846
|
-
const count = liveRoomManager.emitToRoom(
|
|
847
|
-
message.roomId,
|
|
848
|
-
message.event!,
|
|
849
|
-
message.data,
|
|
850
|
-
message.componentId // Excluir quem enviou
|
|
851
|
-
)
|
|
852
|
-
|
|
853
|
-
liveLog('rooms', message.componentId, ` → Notified ${count} components`)
|
|
854
|
-
} catch (error: any) {
|
|
855
|
-
ws.send(JSON.stringify({
|
|
856
|
-
type: 'ERROR',
|
|
857
|
-
componentId: message.componentId,
|
|
858
|
-
roomId: message.roomId,
|
|
859
|
-
error: error.message,
|
|
860
|
-
timestamp: Date.now()
|
|
861
|
-
}))
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
async function handleRoomStateSet(ws: FluxStackWebSocket, message: RoomMessage) {
|
|
866
|
-
liveLog('rooms', message.componentId, `📝 Component ${message.componentId} updating state in room ${message.roomId}`)
|
|
41
|
+
await liveServer.start()
|
|
42
|
+
context.logger.debug('Live Components started via @fluxstack/live')
|
|
43
|
+
},
|
|
867
44
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
message.roomId,
|
|
871
|
-
message.data ?? {},
|
|
872
|
-
message.componentId // Excluir quem enviou
|
|
873
|
-
)
|
|
874
|
-
} catch (error: any) {
|
|
875
|
-
ws.send(JSON.stringify({
|
|
876
|
-
type: 'ERROR',
|
|
877
|
-
componentId: message.componentId,
|
|
878
|
-
roomId: message.roomId,
|
|
879
|
-
error: error.message,
|
|
880
|
-
timestamp: Date.now()
|
|
881
|
-
}))
|
|
45
|
+
onServerStart: async (context: PluginContext) => {
|
|
46
|
+
context.logger.debug('Live Components WebSocket ready on /api/live/ws')
|
|
882
47
|
}
|
|
883
48
|
}
|