create-fluxstack 1.12.0 → 1.13.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 (82) hide show
  1. package/LLMD/INDEX.md +8 -1
  2. package/LLMD/agent.md +867 -0
  3. package/LLMD/config/environment-vars.md +30 -0
  4. package/LLMD/resources/live-auth.md +447 -0
  5. package/LLMD/resources/live-components.md +79 -21
  6. package/LLMD/resources/live-logging.md +158 -0
  7. package/LLMD/resources/live-upload.md +1 -1
  8. package/LLMD/resources/rest-auth.md +290 -0
  9. package/README.md +520 -340
  10. package/app/client/src/App.tsx +11 -0
  11. package/app/client/src/components/AppLayout.tsx +1 -0
  12. package/app/client/src/live/AuthDemo.tsx +332 -0
  13. package/app/client/src/live/RoomChatDemo.tsx +24 -105
  14. package/app/server/auth/AuthManager.ts +213 -0
  15. package/app/server/auth/DevAuthProvider.ts +66 -0
  16. package/app/server/auth/HashManager.ts +123 -0
  17. package/app/server/auth/JWTAuthProvider.example.ts +101 -0
  18. package/app/server/auth/RateLimiter.ts +106 -0
  19. package/app/server/auth/contracts.ts +192 -0
  20. package/app/server/auth/guards/SessionGuard.ts +167 -0
  21. package/app/server/auth/guards/TokenGuard.ts +202 -0
  22. package/app/server/auth/index.ts +174 -0
  23. package/app/server/auth/middleware.ts +163 -0
  24. package/app/server/auth/providers/InMemoryProvider.ts +162 -0
  25. package/app/server/auth/sessions/SessionManager.ts +164 -0
  26. package/app/server/cache/CacheManager.ts +81 -0
  27. package/app/server/cache/MemoryDriver.ts +112 -0
  28. package/app/server/cache/contracts.ts +49 -0
  29. package/app/server/cache/index.ts +42 -0
  30. package/app/server/index.ts +14 -0
  31. package/app/server/live/LiveAdminPanel.ts +173 -0
  32. package/app/server/live/LiveCounter.ts +1 -0
  33. package/app/server/live/LiveLocalCounter.ts +13 -8
  34. package/app/server/live/LiveProtectedChat.ts +150 -0
  35. package/app/server/live/LiveRoomChat.ts +45 -203
  36. package/app/server/routes/auth.routes.ts +278 -0
  37. package/app/server/routes/index.ts +2 -0
  38. package/config/index.ts +8 -0
  39. package/config/system/auth.config.ts +49 -0
  40. package/config/system/session.config.ts +33 -0
  41. package/core/client/LiveComponentsProvider.tsx +76 -5
  42. package/core/client/components/Live.tsx +2 -1
  43. package/core/client/hooks/useLiveComponent.ts +47 -4
  44. package/core/client/index.ts +2 -1
  45. package/core/framework/server.ts +36 -4
  46. package/core/plugins/built-in/live-components/commands/create-live-component.ts +15 -8
  47. package/core/plugins/built-in/monitoring/index.ts +10 -3
  48. package/core/plugins/built-in/vite/index.ts +95 -18
  49. package/core/plugins/config.ts +5 -4
  50. package/core/plugins/discovery.ts +11 -2
  51. package/core/plugins/manager.ts +11 -5
  52. package/core/plugins/module-resolver.ts +1 -1
  53. package/core/plugins/registry.ts +53 -25
  54. package/core/server/live/ComponentRegistry.ts +79 -24
  55. package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
  56. package/core/server/live/LiveLogger.ts +111 -0
  57. package/core/server/live/LiveRoomManager.ts +5 -4
  58. package/core/server/live/StateSignature.ts +644 -643
  59. package/core/server/live/auth/LiveAuthContext.ts +71 -0
  60. package/core/server/live/auth/LiveAuthManager.ts +304 -0
  61. package/core/server/live/auth/index.ts +19 -0
  62. package/core/server/live/auth/types.ts +179 -0
  63. package/core/server/live/auto-generated-components.ts +8 -2
  64. package/core/server/live/index.ts +16 -0
  65. package/core/server/live/websocket-plugin.ts +92 -16
  66. package/core/templates/create-project.ts +0 -3
  67. package/core/types/types.ts +133 -13
  68. package/core/utils/index.ts +17 -17
  69. package/core/utils/logger/index.ts +5 -2
  70. package/core/utils/version.ts +1 -1
  71. package/package.json +1 -8
  72. package/plugins/crypto-auth/index.ts +6 -0
  73. package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
  74. package/plugins/crypto-auth/server/index.ts +24 -21
  75. package/rest-tests/README.md +57 -0
  76. package/rest-tests/auth-token.http +113 -0
  77. package/rest-tests/auth.http +112 -0
  78. package/rest-tests/rooms-token.http +69 -0
  79. package/rest-tests/users-token.http +62 -0
  80. package/.dockerignore +0 -81
  81. package/Dockerfile +0 -70
  82. package/LIVE_COMPONENTS_REVIEW.md +0 -781
@@ -5,10 +5,13 @@ import { fileUploadManager } from './FileUploadManager'
5
5
  import { connectionManager } from './WebSocketConnectionManager'
6
6
  import { performanceMonitor } from './LiveComponentPerformanceMonitor'
7
7
  import { liveRoomManager, type RoomMessage } from './LiveRoomManager'
8
+ import { liveAuthManager } from './auth/LiveAuthManager'
9
+ import { ANONYMOUS_CONTEXT } from './auth/LiveAuthContext'
8
10
  import type { LiveMessage, FileUploadStartMessage, FileUploadChunkMessage, FileUploadCompleteMessage, BinaryChunkHeader, FluxStackWebSocket, FluxStackWSData } from '@core/types/types'
9
11
  import type { Plugin, PluginContext } from '@core/index'
10
12
  import { t, Elysia } from 'elysia'
11
13
  import path from 'path'
14
+ import { liveLog } from './LiveLogger'
12
15
 
13
16
  // ===== Response Schemas for Live Components Routes =====
14
17
 
@@ -138,10 +141,10 @@ export const liveComponentsPlugin: Plugin = {
138
141
  // Binary messages will be ArrayBuffer/Uint8Array, JSON will be parsed objects
139
142
  body: t.Any(),
140
143
 
141
- open(ws) {
144
+ async open(ws) {
142
145
  const socket = ws as unknown as FluxStackWebSocket
143
146
  const connectionId = `ws-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
144
- console.log(`🔌 Live Components WebSocket connected: ${connectionId}`)
147
+ liveLog('websocket', null, `🔌 Live Components WebSocket connected: ${connectionId}`)
145
148
 
146
149
  // Register connection with enhanced connection manager
147
150
  connectionManager.registerConnection(ws as unknown as FluxStackWebSocket, connectionId, 'live-components')
@@ -164,16 +167,35 @@ export const liveComponentsPlugin: Plugin = {
164
167
  socket.data.connectedAt = new Date()
165
168
  }
166
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
+
167
186
  // Send connection confirmation
168
187
  ws.send(JSON.stringify({
169
188
  type: 'CONNECTION_ESTABLISHED',
170
189
  connectionId,
171
190
  timestamp: Date.now(),
191
+ authenticated: socket.data.authContext?.authenticated ?? false,
192
+ userId: socket.data.authContext?.user?.id,
172
193
  features: {
173
194
  compression: true,
174
195
  encryption: true,
175
196
  offlineQueue: true,
176
- loadBalancing: true
197
+ loadBalancing: true,
198
+ auth: liveAuthManager.hasProviders()
177
199
  }
178
200
  }))
179
201
  },
@@ -201,7 +223,7 @@ export const liveComponentsPlugin: Plugin = {
201
223
  // Extract binary chunk data
202
224
  binaryChunkData = buffer.slice(4 + headerLength)
203
225
 
204
- console.log(`📦 Binary chunk received: ${binaryChunkData.length} bytes for upload ${header.uploadId}`)
226
+ liveLog('messages', null, `📦 Binary chunk received: ${binaryChunkData.length} bytes for upload ${header.uploadId}`)
205
227
 
206
228
  // Create message with binary data attached
207
229
  message = {
@@ -215,7 +237,7 @@ export const liveComponentsPlugin: Plugin = {
215
237
  message.timestamp = Date.now()
216
238
  }
217
239
 
218
- console.log(`📨 Received message:`, {
240
+ liveLog('messages', message.componentId || null, `📨 Received message:`, {
219
241
  type: message.type,
220
242
  componentId: message.componentId,
221
243
  action: message.action,
@@ -243,6 +265,9 @@ export const liveComponentsPlugin: Plugin = {
243
265
  case 'COMPONENT_PING':
244
266
  await handleComponentPing(socket, message)
245
267
  break
268
+ case 'AUTH':
269
+ await handleAuth(socket, message)
270
+ break
246
271
  case 'FILE_UPLOAD_START':
247
272
  await handleFileUploadStart(socket, message as FileUploadStartMessage)
248
273
  break
@@ -286,7 +311,7 @@ export const liveComponentsPlugin: Plugin = {
286
311
  close(ws) {
287
312
  const socket = ws as unknown as FluxStackWebSocket
288
313
  const connectionId = socket.data?.connectionId
289
- console.log(`🔌 Live Components WebSocket disconnected: ${connectionId}`)
314
+ liveLog('websocket', null, `🔌 Live Components WebSocket disconnected: ${connectionId}`)
290
315
 
291
316
  // Cleanup connection in connection manager
292
317
  if (connectionId) {
@@ -514,7 +539,7 @@ async function handleComponentMount(ws: FluxStackWebSocket, message: LiveMessage
514
539
  }
515
540
 
516
541
  async function handleComponentRehydrate(ws: FluxStackWebSocket, message: LiveMessage) {
517
- console.log('🔄 Processing component re-hydration request:', {
542
+ liveLog('lifecycle', message.componentId, '🔄 Processing component re-hydration request:', {
518
543
  componentId: message.componentId,
519
544
  payload: message.payload
520
545
  })
@@ -547,7 +572,7 @@ async function handleComponentRehydrate(ws: FluxStackWebSocket, message: LiveMes
547
572
  timestamp: Date.now()
548
573
  }
549
574
 
550
- console.log('📤 Sending COMPONENT_REHYDRATED response:', {
575
+ liveLog('lifecycle', message.componentId, '📤 Sending COMPONENT_REHYDRATED response:', {
551
576
  type: response.type,
552
577
  success: response.success,
553
578
  newComponentId: response.result?.newComponentId,
@@ -638,9 +663,60 @@ async function handleComponentPing(ws: FluxStackWebSocket, message: LiveMessage)
638
663
  ws.send(JSON.stringify(response))
639
664
  }
640
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
+
675
+ if (!liveAuthManager.hasProviders()) {
676
+ ws.send(JSON.stringify({
677
+ type: 'AUTH_RESPONSE',
678
+ success: false,
679
+ error: 'No auth providers configured',
680
+ requestId: message.requestId,
681
+ timestamp: Date.now()
682
+ }))
683
+ return
684
+ }
685
+
686
+ const authContext = await liveAuthManager.authenticate(credentials, providerName)
687
+
688
+ // Store auth context on the WebSocket connection
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
+
641
717
  // File Upload Handler Functions
642
718
  async function handleFileUploadStart(ws: FluxStackWebSocket, message: FileUploadStartMessage) {
643
- console.log('📤 Starting file upload:', message.uploadId)
719
+ liveLog('messages', message.componentId || null, '📤 Starting file upload:', message.uploadId)
644
720
 
645
721
  const result = await fileUploadManager.startUpload(message)
646
722
 
@@ -658,7 +734,7 @@ async function handleFileUploadStart(ws: FluxStackWebSocket, message: FileUpload
658
734
  }
659
735
 
660
736
  async function handleFileUploadChunk(ws: FluxStackWebSocket, message: FileUploadChunkMessage, binaryData: Buffer | null = null) {
661
- console.log(`📦 Receiving chunk ${message.chunkIndex + 1} for upload ${message.uploadId}${binaryData ? ' (binary)' : ' (base64)'}`)
737
+ liveLog('messages', message.componentId || null, `📦 Receiving chunk ${message.chunkIndex + 1} for upload ${message.uploadId}${binaryData ? ' (binary)' : ' (base64)'}`)
662
738
 
663
739
  const progressResponse = await fileUploadManager.receiveChunk(message, ws, binaryData)
664
740
 
@@ -686,7 +762,7 @@ async function handleFileUploadChunk(ws: FluxStackWebSocket, message: FileUpload
686
762
  }
687
763
 
688
764
  async function handleFileUploadComplete(ws: FluxStackWebSocket, message: FileUploadCompleteMessage) {
689
- console.log('✅ Completing file upload:', message.uploadId)
765
+ liveLog('messages', null, '✅ Completing file upload:', message.uploadId)
690
766
 
691
767
  const completeResponse = await fileUploadManager.completeUpload(message)
692
768
 
@@ -702,7 +778,7 @@ async function handleFileUploadComplete(ws: FluxStackWebSocket, message: FileUpl
702
778
  // ===== Room System Handlers =====
703
779
 
704
780
  async function handleRoomJoin(ws: FluxStackWebSocket, message: RoomMessage) {
705
- console.log(`🚪 Component ${message.componentId} joining room ${message.roomId}`)
781
+ liveLog('rooms', message.componentId, `🚪 Component ${message.componentId} joining room ${message.roomId}`)
706
782
 
707
783
  try {
708
784
  const result = liveRoomManager.joinRoom(
@@ -736,7 +812,7 @@ async function handleRoomJoin(ws: FluxStackWebSocket, message: RoomMessage) {
736
812
  }
737
813
 
738
814
  async function handleRoomLeave(ws: FluxStackWebSocket, message: RoomMessage) {
739
- console.log(`🚶 Component ${message.componentId} leaving room ${message.roomId}`)
815
+ liveLog('rooms', message.componentId, `🚶 Component ${message.componentId} leaving room ${message.roomId}`)
740
816
 
741
817
  try {
742
818
  liveRoomManager.leaveRoom(message.componentId, message.roomId)
@@ -764,7 +840,7 @@ async function handleRoomLeave(ws: FluxStackWebSocket, message: RoomMessage) {
764
840
  }
765
841
 
766
842
  async function handleRoomEmit(ws: FluxStackWebSocket, message: RoomMessage) {
767
- console.log(`📡 Component ${message.componentId} emitting '${message.event}' to room ${message.roomId}`)
843
+ liveLog('rooms', message.componentId, `📡 Component ${message.componentId} emitting '${message.event}' to room ${message.roomId}`)
768
844
 
769
845
  try {
770
846
  const count = liveRoomManager.emitToRoom(
@@ -774,7 +850,7 @@ async function handleRoomEmit(ws: FluxStackWebSocket, message: RoomMessage) {
774
850
  message.componentId // Excluir quem enviou
775
851
  )
776
852
 
777
- console.log(` → Notified ${count} components`)
853
+ liveLog('rooms', message.componentId, ` → Notified ${count} components`)
778
854
  } catch (error: any) {
779
855
  ws.send(JSON.stringify({
780
856
  type: 'ERROR',
@@ -787,7 +863,7 @@ async function handleRoomEmit(ws: FluxStackWebSocket, message: RoomMessage) {
787
863
  }
788
864
 
789
865
  async function handleRoomStateSet(ws: FluxStackWebSocket, message: RoomMessage) {
790
- console.log(`📝 Component ${message.componentId} updating state in room ${message.roomId}`)
866
+ liveLog('rooms', message.componentId, `📝 Component ${message.componentId} updating state in room ${message.roomId}`)
791
867
 
792
868
  try {
793
869
  liveRoomManager.setRoomState(
@@ -160,7 +160,6 @@ export class ProjectCreator {
160
160
  "@types/react": "^18.2.0",
161
161
  "@types/react-dom": "^18.2.0",
162
162
  "@types/uuid": "^10.0.0",
163
- "@types/ws": "^8.18.1",
164
163
  "@testing-library/react": "^14.0.0",
165
164
  "@testing-library/jest-dom": "^6.1.0",
166
165
  "@testing-library/user-event": "^14.5.0",
@@ -175,14 +174,12 @@ export class ProjectCreator {
175
174
  "@sinclair/typebox": "^0.34.41",
176
175
  "@vitejs/plugin-react": "^4.0.0",
177
176
  "chalk": "^5.3.0",
178
- "chokidar": "^4.0.3",
179
177
  "elysia": "latest",
180
178
  "react": "^18.2.0",
181
179
  "react-dom": "^18.2.0",
182
180
  "react-icons": "^5.5.0",
183
181
  "uuid": "^13.0.0",
184
182
  "vite": "^5.0.0",
185
- "ws": "^8.18.3",
186
183
  "zustand": "^5.0.8"
187
184
  }
188
185
  }
@@ -2,6 +2,9 @@
2
2
 
3
3
  import { roomEvents } from '@core/server/live/RoomEventBus'
4
4
  import { liveRoomManager } from '@core/server/live/LiveRoomManager'
5
+ import { ANONYMOUS_CONTEXT } from '@core/server/live/auth/LiveAuthContext'
6
+ import { liveLog, liveWarn } from '@core/server/live/LiveLogger'
7
+ import type { LiveAuthContext, LiveComponentAuth, LiveActionAuthMap } from '@core/server/live/auth/types'
5
8
  import type { ServerWebSocket } from 'bun'
6
9
 
7
10
  // ============================================
@@ -18,6 +21,8 @@ export interface FluxStackWSData {
18
21
  subscriptions: Set<string>
19
22
  connectedAt: Date
20
23
  userId?: string
24
+ /** Contexto de autenticação da conexão WebSocket */
25
+ authContext?: LiveAuthContext
21
26
  }
22
27
 
23
28
  /**
@@ -46,9 +51,11 @@ export type FluxStackServerWebSocket = ServerWebSocket<FluxStackWSData>
46
51
  export interface LiveMessage {
47
52
  type: 'COMPONENT_MOUNT' | 'COMPONENT_UNMOUNT' |
48
53
  'COMPONENT_REHYDRATE' | 'COMPONENT_ACTION' | 'CALL_ACTION' |
49
- 'ACTION_RESPONSE' | 'PROPERTY_UPDATE' | 'STATE_UPDATE' | 'STATE_REHYDRATED' |
54
+ 'ACTION_RESPONSE' | 'PROPERTY_UPDATE' | 'STATE_UPDATE' | 'STATE_DELTA' | 'STATE_REHYDRATED' |
50
55
  'ERROR' | 'BROADCAST' | 'FILE_UPLOAD_START' | 'FILE_UPLOAD_CHUNK' | 'FILE_UPLOAD_COMPLETE' |
51
56
  'COMPONENT_PING' | 'COMPONENT_PONG' |
57
+ // Auth system message
58
+ 'AUTH' |
52
59
  // Room system messages
53
60
  'ROOM_JOIN' | 'ROOM_LEAVE' | 'ROOM_EMIT' | 'ROOM_STATE_SET' | 'ROOM_STATE_GET'
54
61
  componentId: string
@@ -117,7 +124,9 @@ export interface WebSocketMessage {
117
124
  }
118
125
 
119
126
  export interface WebSocketResponse {
120
- type: 'MESSAGE_RESPONSE' | 'CONNECTION_ESTABLISHED' | 'ERROR' | 'BROADCAST' | 'ACTION_RESPONSE' | 'COMPONENT_MOUNTED' | 'COMPONENT_REHYDRATED' | 'STATE_UPDATE' | 'STATE_REHYDRATED' | 'FILE_UPLOAD_PROGRESS' | 'FILE_UPLOAD_COMPLETE' | 'FILE_UPLOAD_ERROR' | 'FILE_UPLOAD_START_RESPONSE' | 'COMPONENT_PONG' |
127
+ type: 'MESSAGE_RESPONSE' | 'CONNECTION_ESTABLISHED' | 'ERROR' | 'BROADCAST' | 'ACTION_RESPONSE' | 'COMPONENT_MOUNTED' | 'COMPONENT_REHYDRATED' | 'STATE_UPDATE' | 'STATE_DELTA' | 'STATE_REHYDRATED' | 'FILE_UPLOAD_PROGRESS' | 'FILE_UPLOAD_COMPLETE' | 'FILE_UPLOAD_ERROR' | 'FILE_UPLOAD_START_RESPONSE' | 'COMPONENT_PONG' |
128
+ // Auth system response
129
+ 'AUTH_RESPONSE' |
121
130
  // Room system responses
122
131
  'ROOM_EVENT' | 'ROOM_STATE' | 'ROOM_SYSTEM' | 'ROOM_JOINED' | 'ROOM_LEFT'
123
132
  originalType?: string
@@ -214,6 +223,48 @@ export abstract class LiveComponent<TState = ComponentState> {
214
223
  /** Default state - must be defined in subclasses */
215
224
  static defaultState: any
216
225
 
226
+ /**
227
+ * Per-component logging control. Silent by default.
228
+ *
229
+ * @example
230
+ * // Enable all log categories
231
+ * static logging = true
232
+ *
233
+ * // Enable specific categories only
234
+ * static logging = ['lifecycle', 'messages'] as const
235
+ *
236
+ * // Disabled (default — omit or set false)
237
+ * static logging = false
238
+ *
239
+ * Categories: 'lifecycle' | 'messages' | 'state' | 'performance' | 'rooms' | 'websocket'
240
+ */
241
+ static logging?: boolean | readonly ('lifecycle' | 'messages' | 'state' | 'performance' | 'rooms' | 'websocket')[]
242
+
243
+ /**
244
+ * Configuração de autenticação do componente.
245
+ * Define se auth é obrigatória e quais roles/permissions são necessárias.
246
+ *
247
+ * @example
248
+ * static auth: LiveComponentAuth = {
249
+ * required: true,
250
+ * roles: ['admin', 'moderator'],
251
+ * permissions: ['chat.read'],
252
+ * }
253
+ */
254
+ static auth?: LiveComponentAuth
255
+
256
+ /**
257
+ * Configuração de autenticação por action.
258
+ * Permite controle granular de permissões por método.
259
+ *
260
+ * @example
261
+ * static actionAuth: LiveActionAuthMap = {
262
+ * deleteMessage: { permissions: ['chat.admin'] },
263
+ * sendMessage: { permissions: ['chat.write'] },
264
+ * }
265
+ */
266
+ static actionAuth?: LiveActionAuthMap
267
+
217
268
  public readonly id: string
218
269
  private _state: TState
219
270
  public state: TState // Proxy wrapper
@@ -222,6 +273,9 @@ export abstract class LiveComponent<TState = ComponentState> {
222
273
  public userId?: string
223
274
  public broadcastToRoom: (message: BroadcastMessage) => void = () => {} // Will be injected by registry
224
275
 
276
+ // Auth context (injected by registry during mount)
277
+ private _authContext: LiveAuthContext = ANONYMOUS_CONTEXT
278
+
225
279
  // Room event subscriptions (cleaned up on destroy)
226
280
  private roomEventUnsubscribers: (() => void)[] = []
227
281
  private joinedRooms: Set<string> = new Set()
@@ -250,9 +304,38 @@ export abstract class LiveComponent<TState = ComponentState> {
250
304
  this.joinedRooms.add(this.room)
251
305
  liveRoomManager.joinRoom(this.id, this.room, this.ws)
252
306
  }
307
+
308
+ // 🔥 Create direct property accessors (this.count instead of this.state.count)
309
+ this.createDirectStateAccessors()
253
310
  }
254
311
 
255
- // Create a Proxy that auto-emits STATE_UPDATE on any mutation
312
+ // Create getters/setters for each state property directly on `this`
313
+ private createDirectStateAccessors() {
314
+ // Properties that should NOT become state accessors
315
+ const forbidden = new Set([
316
+ // Instance properties
317
+ ...Object.keys(this),
318
+ // Prototype methods
319
+ ...Object.getOwnPropertyNames(Object.getPrototypeOf(this)),
320
+ // Known internal properties
321
+ 'state', '_state', 'ws', 'id', 'room', 'userId', 'broadcastToRoom',
322
+ '$room', '$rooms', 'roomType', 'roomHandles', 'joinedRooms', 'roomEventUnsubscribers'
323
+ ])
324
+
325
+ // Create accessor for each state key
326
+ for (const key of Object.keys(this._state as object)) {
327
+ if (!forbidden.has(key)) {
328
+ Object.defineProperty(this, key, {
329
+ get: () => (this._state as any)[key],
330
+ set: (value) => { (this.state as any)[key] = value }, // Uses proxy for auto-sync
331
+ enumerable: true,
332
+ configurable: true
333
+ })
334
+ }
335
+ }
336
+ }
337
+
338
+ // Create a Proxy that auto-emits STATE_DELTA on any mutation
256
339
  private createStateProxy(state: TState): TState {
257
340
  const self = this
258
341
  return new Proxy(state as object, {
@@ -260,8 +343,8 @@ export abstract class LiveComponent<TState = ComponentState> {
260
343
  const oldValue = (target as any)[prop]
261
344
  if (oldValue !== value) {
262
345
  (target as any)[prop] = value
263
- // Auto-sync to frontend
264
- self.emit('STATE_UPDATE', { state: self._state })
346
+ // Delta sync - send only the changed property
347
+ self.emit('STATE_DELTA', { delta: { [prop]: value } })
265
348
  }
266
349
  return true
267
350
  },
@@ -388,11 +471,48 @@ export abstract class LiveComponent<TState = ComponentState> {
388
471
  return Array.from(this.joinedRooms)
389
472
  }
390
473
 
391
- // State management (batch update - single emit)
474
+ // ========================================
475
+ // 🔒 $auth - Contexto de Autenticação
476
+ // ========================================
477
+
478
+ /**
479
+ * Acessa o contexto de autenticação do usuário atual.
480
+ * Disponível após o mount do componente.
481
+ *
482
+ * @example
483
+ * async sendMessage(payload: { text: string }) {
484
+ * if (!this.$auth.authenticated) {
485
+ * throw new Error('Login required')
486
+ * }
487
+ *
488
+ * const userId = this.$auth.user!.id
489
+ * const isAdmin = this.$auth.hasRole('admin')
490
+ * const canDelete = this.$auth.hasPermission('chat.admin')
491
+ * }
492
+ */
493
+ public get $auth(): LiveAuthContext {
494
+ return this._authContext
495
+ }
496
+
497
+ /**
498
+ * Injeta o contexto de autenticação no componente.
499
+ * Chamado internamente pelo ComponentRegistry durante o mount.
500
+ * @internal
501
+ */
502
+ public setAuthContext(context: LiveAuthContext): void {
503
+ this._authContext = context
504
+ // Atualiza userId se disponível no auth context
505
+ if (context.authenticated && context.user?.id && !this.userId) {
506
+ this.userId = context.user.id
507
+ }
508
+ }
509
+
510
+ // State management (batch update - single emit with delta)
392
511
  public setState(updates: Partial<TState> | ((prev: TState) => Partial<TState>)) {
393
512
  const newUpdates = typeof updates === 'function' ? updates(this._state) : updates
394
513
  Object.assign(this._state as object, newUpdates)
395
- this.emit('STATE_UPDATE', { state: this._state })
514
+ // Delta sync - send only the changed properties
515
+ this.emit('STATE_DELTA', { delta: newUpdates })
396
516
  }
397
517
 
398
518
  // Generic setValue action - set any state key with type safety
@@ -444,7 +564,7 @@ export abstract class LiveComponent<TState = ComponentState> {
444
564
  // Broadcast to all clients in room (via WebSocket)
445
565
  protected broadcast(type: string, payload: any, excludeCurrentUser = false) {
446
566
  if (!this.room) {
447
- console.warn(`⚠️ [${this.id}] Cannot broadcast '${type}' - no room set`)
567
+ liveWarn('rooms', this.id, `⚠️ [${this.id}] Cannot broadcast '${type}' - no room set`)
448
568
  return
449
569
  }
450
570
 
@@ -455,7 +575,7 @@ export abstract class LiveComponent<TState = ComponentState> {
455
575
  excludeUser: excludeCurrentUser ? this.userId : undefined
456
576
  }
457
577
 
458
- console.log(`📤 [${this.id}] Broadcasting '${type}' to room '${this.room}'`)
578
+ liveLog('rooms', this.id, `📤 [${this.id}] Broadcasting '${type}' to room '${this.room}'`)
459
579
 
460
580
  // This will be handled by the registry
461
581
  this.broadcastToRoom(message)
@@ -475,14 +595,14 @@ export abstract class LiveComponent<TState = ComponentState> {
475
595
  */
476
596
  protected emitRoomEvent(event: string, data: any, notifySelf = false): number {
477
597
  if (!this.room) {
478
- console.warn(`⚠️ [${this.id}] Cannot emit room event '${event}' - no room set`)
598
+ liveWarn('rooms', this.id, `⚠️ [${this.id}] Cannot emit room event '${event}' - no room set`)
479
599
  return 0
480
600
  }
481
601
 
482
602
  const excludeId = notifySelf ? undefined : this.id
483
603
  const notified = roomEvents.emit(this.roomType, this.room, event, data, excludeId)
484
604
 
485
- console.log(`📡 [${this.id}] Room event '${event}' → ${notified} components`)
605
+ liveLog('rooms', this.id, `📡 [${this.id}] Room event '${event}' → ${notified} components`)
486
606
  return notified
487
607
  }
488
608
 
@@ -495,7 +615,7 @@ export abstract class LiveComponent<TState = ComponentState> {
495
615
  */
496
616
  protected onRoomEvent<T = any>(event: string, handler: (data: T) => void): void {
497
617
  if (!this.room) {
498
- console.warn(`⚠️ [${this.id}] Cannot subscribe to room event '${event}' - no room set`)
618
+ liveWarn('rooms', this.id, `⚠️ [${this.id}] Cannot subscribe to room event '${event}' - no room set`)
499
619
  return
500
620
  }
501
621
 
@@ -510,7 +630,7 @@ export abstract class LiveComponent<TState = ComponentState> {
510
630
  // Guardar para cleanup no destroy
511
631
  this.roomEventUnsubscribers.push(unsubscribe)
512
632
 
513
- console.log(`👂 [${this.id}] Subscribed to room event '${event}'`)
633
+ liveLog('rooms', this.id, `👂 [${this.id}] Subscribed to room event '${event}'`)
514
634
  }
515
635
 
516
636
  /**
@@ -1,18 +1,18 @@
1
- /**
2
- * FluxStack Utilities
3
- * Main exports for utility functions and classes
4
- */
5
-
6
- // Logger utilities
7
- export { logger, log } from "./logger"
8
- export type { Logger } from "./logger/index"
9
-
10
- // Error handling
11
- export * from "./errors"
12
-
13
- // Monitoring
14
- export { MetricsCollector } from "./monitoring"
15
- export type * from "./monitoring"
16
-
17
- // General helpers
1
+ /**
2
+ * FluxStack Utilities
3
+ * Main exports for utility functions and classes
4
+ */
5
+
6
+ // Logger utilities
7
+ export { logger, log } from "./logger"
8
+ export type { Logger } from "./logger/index"
9
+
10
+ // Error handling
11
+ export * from "./errors"
12
+
13
+ // Monitoring
14
+ export { MetricsCollector } from "./monitoring"
15
+ export type * from "./monitoring"
16
+
17
+ // General helpers
18
18
  export * from "./helpers"
@@ -127,7 +127,7 @@ export function SECTION(sectionName: string, callback: () => void): void {
127
127
  /**
128
128
  * HTTP request logging with colors and formatting
129
129
  */
130
- export function request(method: string, path: string, status?: number, duration?: number): void {
130
+ export function request(method: string, path: string, status?: number, duration?: number, ip?: string): void {
131
131
  const { file: callerFile } = getCallerInfo()
132
132
  const logger = getLoggerForModule(callerFile)
133
133
 
@@ -159,12 +159,15 @@ export function request(method: string, path: string, status?: number, duration?
159
159
  durationStr = ` ${durationColor(`(${duration}ms)`)}`
160
160
  }
161
161
 
162
+ // Format IP address
163
+ const ipStr = ip ? chalk.dim(`[${ip}]`) + ' ' : ''
164
+
162
165
  // Build log message
163
166
  const methodStr = methodColor(method.padEnd(7))
164
167
  const pathStr = chalk.white(path.padEnd(30))
165
168
  const statusStr = status ? `→ ${statusColor(status.toString())}` : ''
166
169
 
167
- const message = `${methodStr} ${pathStr} ${statusStr}${durationStr}`
170
+ const message = `${ipStr}${methodStr} ${pathStr} ${statusStr}${durationStr}`
168
171
 
169
172
  // Use appropriate log level based on status
170
173
  const level = status && status >= 500 ? 'error' : status && status >= 400 ? 'warn' : 'info'
@@ -3,4 +3,4 @@
3
3
  * Single source of truth for version number
4
4
  * Auto-synced with package.json
5
5
  */
6
- export const FLUXSTACK_VERSION = '1.11.0'
6
+ export const FLUXSTACK_VERSION = '1.13.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fluxstack",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "⚡ Revolutionary full-stack TypeScript framework with Declarative Config System, Elysia + React + Bun",
5
5
  "keywords": [
6
6
  "framework",
@@ -55,7 +55,6 @@
55
55
  "@types/react-dom": "^19.1.6",
56
56
  "@vitest/coverage-v8": "^3.2.4",
57
57
  "@vitest/ui": "^3.2.4",
58
- "concurrently": "^9.2.0",
59
58
  "cross-env": "^10.1.0",
60
59
  "eslint": "^9.30.1",
61
60
  "eslint-plugin-react-hooks": "^5.2.0",
@@ -73,16 +72,11 @@
73
72
  "dependencies": {
74
73
  "@elysiajs/eden": "^1.3.2",
75
74
  "@elysiajs/swagger": "^1.3.1",
76
- "@types/http-proxy-middleware": "^1.0.0",
77
- "@types/ws": "^8.18.1",
78
75
  "@vitejs/plugin-react": "^4.6.0",
79
76
  "chalk": "^5.3.0",
80
- "chokidar": "^4.0.3",
81
77
  "commander": "^12.1.0",
82
78
  "elysia": "^1.4.6",
83
- "http-proxy-middleware": "^3.0.5",
84
79
  "lightningcss": "^1.30.1",
85
- "lucide-react": "^0.544.0",
86
80
  "ora": "^8.1.0",
87
81
  "react": "^19.1.0",
88
82
  "react-dom": "^19.1.0",
@@ -92,7 +86,6 @@
92
86
  "vite": "^7.1.7",
93
87
  "winston": "^3.18.3",
94
88
  "winston-daily-rotate-file": "^5.0.0",
95
- "ws": "^8.18.3",
96
89
  "zustand": "^5.0.8"
97
90
  },
98
91
  "engines": {
@@ -8,6 +8,8 @@ import type { FluxStack, PluginContext, RequestContext, ResponseContext } from "
8
8
  type Plugin = FluxStack.Plugin
9
9
  import { Elysia, t } from "elysia"
10
10
  import { CryptoAuthService, AuthMiddleware } from "./server"
11
+ import { CryptoAuthLiveProvider } from "./server/CryptoAuthLiveProvider"
12
+ import { liveAuthManager } from "@core/server/live/auth"
11
13
  import { makeProtectedRouteCommand } from "./cli/make-protected-route.command"
12
14
 
13
15
  // ✅ Plugin carrega sua própria configuração (da pasta config/ do plugin)
@@ -88,6 +90,10 @@ export const cryptoAuthPlugin: Plugin = {
88
90
  ;(global as any).cryptoAuthService = authService
89
91
  ;(global as any).cryptoAuthMiddleware = authMiddleware
90
92
 
93
+ // 🔒 Register as LiveAuthProvider for Live Components WebSocket auth
94
+ liveAuthManager.register(new CryptoAuthLiveProvider(authService))
95
+ context.logger.info('🔒 Crypto Auth registered as Live Components auth provider')
96
+
91
97
  // Store plugin info for table display
92
98
  if (!(global as any).__fluxstackPlugins) {
93
99
  (global as any).__fluxstackPlugins = []