create-fluxstack 1.5.5 → 1.7.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 (60) 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/cli/command-registry.ts +10 -10
  17. package/core/cli/commands/plugin-deps.ts +13 -5
  18. package/core/cli/plugin-discovery.ts +1 -1
  19. package/core/client/LiveComponentsProvider.tsx +414 -0
  20. package/core/client/hooks/useHybridLiveComponent.ts +194 -530
  21. package/core/client/index.ts +16 -0
  22. package/core/framework/server.ts +144 -63
  23. package/core/index.ts +4 -1
  24. package/core/plugins/built-in/monitoring/index.ts +1 -1
  25. package/core/plugins/built-in/static/index.ts +1 -1
  26. package/core/plugins/built-in/swagger/index.ts +1 -1
  27. package/core/plugins/built-in/vite/index.ts +1 -1
  28. package/core/plugins/config.ts +1 -1
  29. package/core/plugins/discovery.ts +1 -1
  30. package/core/plugins/executor.ts +1 -1
  31. package/core/plugins/index.ts +1 -0
  32. package/core/server/live/ComponentRegistry.ts +3 -1
  33. package/core/server/live/WebSocketConnectionManager.ts +14 -4
  34. package/core/server/live/websocket-plugin.ts +453 -434
  35. package/core/server/middleware/elysia-helpers.ts +3 -5
  36. package/core/server/plugins/database.ts +1 -1
  37. package/core/server/plugins/static-files-plugin.ts +1 -1
  38. package/core/types/index.ts +1 -1
  39. package/core/types/plugin.ts +1 -1
  40. package/core/types/types.ts +6 -2
  41. package/core/utils/logger/colors.ts +4 -4
  42. package/core/utils/logger/index.ts +37 -4
  43. package/core/utils/logger/winston-logger.ts +1 -1
  44. package/core/utils/sync-version.ts +61 -0
  45. package/core/utils/version.ts +6 -5
  46. package/package.json +5 -3
  47. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +1 -1
  48. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
  49. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +1 -1
  50. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +1 -1
  51. package/vite.config.ts +8 -0
  52. package/.dockerignore +0 -50
  53. package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +0 -475
  54. package/CRYPTO-AUTH-MIDDLEWARES.md +0 -473
  55. package/CRYPTO-AUTH-USAGE.md +0 -491
  56. package/EXEMPLO-ROTA-PROTEGIDA.md +0 -347
  57. package/QUICK-START-CRYPTO-AUTH.md +0 -221
  58. package/app/client/src/components/Teste.tsx +0 -104
  59. package/app/server/live/TesteComponent.ts +0 -87
  60. package/test-crypto-auth.ts +0 -101
@@ -0,0 +1,414 @@
1
+ // 🔥 Live Components Provider - Singleton WebSocket Connection
2
+ // Single WebSocket connection shared by all live components in the app
3
+
4
+ import React, { createContext, useContext, useEffect, useRef, useState, useCallback } from 'react'
5
+ import type { WebSocketMessage, WebSocketResponse } from '../types/types'
6
+
7
+ export interface LiveComponentsContextValue {
8
+ connected: boolean
9
+ connecting: boolean
10
+ error: string | null
11
+ connectionId: string | null
12
+
13
+ // Send message without waiting for response
14
+ sendMessage: (message: WebSocketMessage) => Promise<void>
15
+
16
+ // Send message and wait for specific response
17
+ sendMessageAndWait: (message: WebSocketMessage, timeout?: number) => Promise<WebSocketResponse>
18
+
19
+ // Register message listener for a component
20
+ registerComponent: (componentId: string, callback: (message: WebSocketResponse) => void) => () => void
21
+
22
+ // Unregister component
23
+ unregisterComponent: (componentId: string) => void
24
+
25
+ // Manual reconnect
26
+ reconnect: () => void
27
+
28
+ // Get current WebSocket instance (for advanced use)
29
+ getWebSocket: () => WebSocket | null
30
+ }
31
+
32
+ const LiveComponentsContext = createContext<LiveComponentsContextValue | null>(null)
33
+
34
+ export interface LiveComponentsProviderProps {
35
+ children: React.ReactNode
36
+ url?: string
37
+ autoConnect?: boolean
38
+ reconnectInterval?: number
39
+ maxReconnectAttempts?: number
40
+ heartbeatInterval?: number
41
+ debug?: boolean
42
+ }
43
+
44
+ export function LiveComponentsProvider({
45
+ children,
46
+ url,
47
+ autoConnect = true,
48
+ reconnectInterval = 1000,
49
+ maxReconnectAttempts = 5,
50
+ heartbeatInterval = 30000,
51
+ debug = false
52
+ }: WebSocketProviderProps) {
53
+
54
+ // Get WebSocket URL dynamically
55
+ const getWebSocketUrl = () => {
56
+ if (url) return url
57
+ if (typeof window === 'undefined') return 'ws://localhost:3000/api/live/ws'
58
+
59
+ const hostname = window.location.hostname
60
+ const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1'
61
+
62
+ if (!isLocalhost) {
63
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
64
+ return `${protocol}//${window.location.host}/api/live/ws`
65
+ }
66
+
67
+ return 'ws://localhost:3000/api/live/ws'
68
+ }
69
+
70
+ const wsUrl = getWebSocketUrl()
71
+
72
+ // State
73
+ const [connected, setConnected] = useState(false)
74
+ const [connecting, setConnecting] = useState(false)
75
+ const [error, setError] = useState<string | null>(null)
76
+ const [connectionId, setConnectionId] = useState<string | null>(null)
77
+
78
+ // Refs
79
+ const wsRef = useRef<WebSocket | null>(null)
80
+ const reconnectAttemptsRef = useRef(0)
81
+ const reconnectTimeoutRef = useRef<number | null>(null)
82
+ const heartbeatIntervalRef = useRef<number | null>(null)
83
+
84
+ // Component callbacks registry: componentId -> callback
85
+ const componentCallbacksRef = useRef<Map<string, (message: WebSocketResponse) => void>>(new Map())
86
+
87
+ // Pending requests: requestId -> { resolve, reject, timeout }
88
+ const pendingRequestsRef = useRef<Map<string, {
89
+ resolve: (value: any) => void
90
+ reject: (error: any) => void
91
+ timeout: NodeJS.Timeout
92
+ }>>(new Map())
93
+
94
+ const log = useCallback((message: string, data?: any) => {
95
+ if (debug) {
96
+ console.log(`[WebSocketProvider] ${message}`, data || '')
97
+ }
98
+ }, [debug])
99
+
100
+ // Generate unique request ID
101
+ const generateRequestId = useCallback(() => {
102
+ return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
103
+ }, [])
104
+
105
+ // Connect to WebSocket
106
+ const connect = useCallback(() => {
107
+ if (wsRef.current?.readyState === WebSocket.CONNECTING) {
108
+ log('Already connecting, skipping...')
109
+ return
110
+ }
111
+
112
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
113
+ log('Already connected, skipping...')
114
+ return
115
+ }
116
+ setConnecting(true)
117
+ setError(null)
118
+ log('🔌 Connecting to WebSocket...', { url: wsUrl })
119
+
120
+ try {
121
+ const ws = new WebSocket(wsUrl)
122
+ wsRef.current = ws
123
+
124
+ ws.onopen = () => {
125
+ log('✅ WebSocket connected')
126
+ setConnected(true)
127
+ setConnecting(false)
128
+ reconnectAttemptsRef.current = 0
129
+
130
+ // Start heartbeat
131
+ startHeartbeat()
132
+ }
133
+
134
+ ws.onmessage = (event) => {
135
+ try {
136
+ const response: WebSocketResponse = JSON.parse(event.data)
137
+ log('📨 Received message', { type: response.type, componentId: response.componentId })
138
+
139
+ // Handle connection established
140
+ if (response.type === 'CONNECTION_ESTABLISHED') {
141
+ setConnectionId(response.connectionId || null)
142
+ log('🔗 Connection ID:', response.connectionId)
143
+ }
144
+
145
+ // Handle pending requests (request-response pattern)
146
+ if (response.requestId && pendingRequestsRef.current.has(response.requestId)) {
147
+ const request = pendingRequestsRef.current.get(response.requestId)!
148
+ clearTimeout(request.timeout)
149
+ pendingRequestsRef.current.delete(response.requestId)
150
+
151
+ if (response.success !== false) {
152
+ request.resolve(response)
153
+ } else {
154
+ // Don't reject re-hydration errors - let component handle them
155
+ if (response.error?.includes?.('COMPONENT_REHYDRATION_REQUIRED')) {
156
+ request.resolve(response)
157
+ } else {
158
+ request.reject(new Error(response.error || 'Request failed'))
159
+ }
160
+ }
161
+ return
162
+ }
163
+
164
+ // Route message to specific component
165
+ if (response.componentId) {
166
+ const callback = componentCallbacksRef.current.get(response.componentId)
167
+ if (callback) {
168
+ callback(response)
169
+ } else {
170
+ log('⚠️ No callback registered for component:', response.componentId)
171
+ }
172
+ }
173
+
174
+ // Broadcast messages (no specific componentId)
175
+ if (response.type === 'BROADCAST' && !response.componentId) {
176
+ // Send to all registered components
177
+ componentCallbacksRef.current.forEach(callback => {
178
+ callback(response)
179
+ })
180
+ }
181
+
182
+ } catch (error) {
183
+ log('❌ Failed to parse message', error)
184
+ setError('Failed to parse message')
185
+ }
186
+ }
187
+
188
+ ws.onclose = () => {
189
+ log('🔌 WebSocket closed')
190
+ setConnected(false)
191
+ setConnecting(false)
192
+ setConnectionId(null)
193
+
194
+ // Stop heartbeat
195
+ stopHeartbeat()
196
+
197
+ // Auto-reconnect
198
+ if (reconnectAttemptsRef.current < maxReconnectAttempts) {
199
+ reconnectAttemptsRef.current++
200
+ log(`🔄 Reconnecting... (${reconnectAttemptsRef.current}/${maxReconnectAttempts})`)
201
+
202
+ reconnectTimeoutRef.current = window.setTimeout(() => {
203
+ connect()
204
+ }, reconnectInterval)
205
+ } else {
206
+ setError('Max reconnection attempts reached')
207
+ log('❌ Max reconnection attempts reached')
208
+ }
209
+ }
210
+
211
+ ws.onerror = (event) => {
212
+ log('❌ WebSocket error', event)
213
+ setError('WebSocket connection error')
214
+ setConnecting(false)
215
+ }
216
+
217
+ } catch (error) {
218
+ setConnecting(false)
219
+ setError(error instanceof Error ? error.message : 'Connection failed')
220
+ log('❌ Failed to create WebSocket', error)
221
+ }
222
+ }, [wsUrl, reconnectInterval, maxReconnectAttempts, log])
223
+
224
+ // Disconnect
225
+ const disconnect = useCallback(() => {
226
+ if (reconnectTimeoutRef.current) {
227
+ clearTimeout(reconnectTimeoutRef.current)
228
+ reconnectTimeoutRef.current = null
229
+ }
230
+
231
+ stopHeartbeat()
232
+
233
+ if (wsRef.current) {
234
+ wsRef.current.close()
235
+ wsRef.current = null
236
+ }
237
+
238
+ reconnectAttemptsRef.current = maxReconnectAttempts // Prevent auto-reconnect
239
+ setConnected(false)
240
+ setConnecting(false)
241
+ setConnectionId(null)
242
+ log('🔌 WebSocket disconnected manually')
243
+ }, [maxReconnectAttempts, log])
244
+
245
+ // Manual reconnect
246
+ const reconnect = useCallback(() => {
247
+ disconnect()
248
+ reconnectAttemptsRef.current = 0
249
+ setTimeout(() => connect(), 100)
250
+ }, [connect, disconnect])
251
+
252
+ // Start heartbeat (ping components periodically)
253
+ const startHeartbeat = useCallback(() => {
254
+ stopHeartbeat()
255
+
256
+ heartbeatIntervalRef.current = window.setInterval(() => {
257
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
258
+ // Send ping to all registered components
259
+ componentCallbacksRef.current.forEach((_, componentId) => {
260
+ sendMessage({
261
+ type: 'COMPONENT_PING',
262
+ componentId,
263
+ timestamp: Date.now()
264
+ }).catch(err => {
265
+ log('❌ Heartbeat ping failed for component:', componentId)
266
+ })
267
+ })
268
+ }
269
+ }, heartbeatInterval)
270
+ }, [heartbeatInterval, log])
271
+
272
+ // Stop heartbeat
273
+ const stopHeartbeat = useCallback(() => {
274
+ if (heartbeatIntervalRef.current) {
275
+ clearInterval(heartbeatIntervalRef.current)
276
+ heartbeatIntervalRef.current = null
277
+ }
278
+ }, [])
279
+
280
+ // Send message without waiting for response
281
+ const sendMessage = useCallback(async (message: WebSocketMessage): Promise<void> => {
282
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
283
+ throw new Error('WebSocket is not connected')
284
+ }
285
+
286
+ try {
287
+ const messageWithTimestamp = { ...message, timestamp: Date.now() }
288
+ wsRef.current.send(JSON.stringify(messageWithTimestamp))
289
+ log('📤 Sent message', { type: message.type, componentId: message.componentId })
290
+ } catch (error) {
291
+ log('❌ Failed to send message', error)
292
+ throw error
293
+ }
294
+ }, [log])
295
+
296
+ // Send message and wait for response
297
+ const sendMessageAndWait = useCallback(async (
298
+ message: WebSocketMessage,
299
+ timeout: number = 10000
300
+ ): Promise<WebSocketResponse> => {
301
+ return new Promise((resolve, reject) => {
302
+ if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
303
+ reject(new Error('WebSocket is not connected'))
304
+ return
305
+ }
306
+
307
+ const requestId = generateRequestId()
308
+
309
+ // Set up timeout
310
+ const timeoutHandle = setTimeout(() => {
311
+ pendingRequestsRef.current.delete(requestId)
312
+ reject(new Error(`Request timeout after ${timeout}ms`))
313
+ }, timeout)
314
+
315
+ // Store pending request
316
+ pendingRequestsRef.current.set(requestId, {
317
+ resolve,
318
+ reject,
319
+ timeout: timeoutHandle
320
+ })
321
+
322
+ try {
323
+ const messageWithRequestId = {
324
+ ...message,
325
+ requestId,
326
+ expectResponse: true,
327
+ timestamp: Date.now()
328
+ }
329
+
330
+ wsRef.current.send(JSON.stringify(messageWithRequestId))
331
+ log('📤 Sent message with request ID', { requestId, type: message.type })
332
+ } catch (error) {
333
+ clearTimeout(timeoutHandle)
334
+ pendingRequestsRef.current.delete(requestId)
335
+ reject(error)
336
+ }
337
+ })
338
+ }, [log, generateRequestId])
339
+
340
+ // Register component callback
341
+ const registerComponent = useCallback((
342
+ componentId: string,
343
+ callback: (message: WebSocketResponse) => void
344
+ ): (() => void) => {
345
+ log('📝 Registering component', componentId)
346
+ componentCallbacksRef.current.set(componentId, callback)
347
+
348
+ // Return unregister function
349
+ return () => {
350
+ log('🗑️ Unregistering component', componentId)
351
+ componentCallbacksRef.current.delete(componentId)
352
+ }
353
+ }, [log])
354
+
355
+ // Unregister component
356
+ const unregisterComponent = useCallback((componentId: string) => {
357
+ componentCallbacksRef.current.delete(componentId)
358
+ log('🗑️ Component unregistered', componentId)
359
+ }, [log])
360
+
361
+ // Get WebSocket instance
362
+ const getWebSocket = useCallback(() => {
363
+ return wsRef.current
364
+ }, [])
365
+
366
+ // Auto-connect on mount
367
+ useEffect(() => {
368
+ if (autoConnect) {
369
+ connect()
370
+ }
371
+
372
+ return () => {
373
+ disconnect()
374
+ }
375
+ }, [autoConnect, connect, disconnect])
376
+
377
+ const value: LiveComponentsContextValue = {
378
+ connected,
379
+ connecting,
380
+ error,
381
+ connectionId,
382
+ sendMessage,
383
+ sendMessageAndWait,
384
+ registerComponent,
385
+ unregisterComponent,
386
+ reconnect,
387
+ getWebSocket
388
+ }
389
+
390
+ return (
391
+ <LiveComponentsContext.Provider value={value}>
392
+ {children}
393
+ </LiveComponentsContext.Provider>
394
+ )
395
+ }
396
+
397
+ // Hook to use Live Components context
398
+ export function useLiveComponents(): LiveComponentsContextValue {
399
+ const context = useContext(LiveComponentsContext)
400
+ if (!context) {
401
+ throw new Error('useLiveComponents must be used within LiveComponentsProvider')
402
+ }
403
+ return context
404
+ }
405
+
406
+ // ⚠️ DEPRECATED: Use useLiveComponents instead
407
+ // Kept for backward compatibility
408
+ export const useWebSocketContext = useLiveComponents
409
+
410
+ // ⚠️ DEPRECATED: Use LiveComponentsProvider instead
411
+ // Kept for backward compatibility
412
+ export const WebSocketProvider = LiveComponentsProvider
413
+ export type WebSocketProviderProps = LiveComponentsProviderProps
414
+ export type WebSocketContextValue = LiveComponentsContextValue