create-fluxstack 1.10.1 → 1.12.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 (257) hide show
  1. package/.dockerignore +1 -2
  2. package/Dockerfile +8 -8
  3. package/LLMD/INDEX.md +64 -0
  4. package/LLMD/MAINTENANCE.md +197 -0
  5. package/LLMD/MIGRATION.md +156 -0
  6. package/LLMD/config/.gitkeep +1 -0
  7. package/LLMD/config/declarative-system.md +268 -0
  8. package/LLMD/config/environment-vars.md +327 -0
  9. package/LLMD/config/runtime-reload.md +401 -0
  10. package/LLMD/core/.gitkeep +1 -0
  11. package/LLMD/core/build-system.md +599 -0
  12. package/LLMD/core/framework-lifecycle.md +229 -0
  13. package/LLMD/core/plugin-system.md +451 -0
  14. package/LLMD/patterns/.gitkeep +1 -0
  15. package/LLMD/patterns/anti-patterns.md +297 -0
  16. package/LLMD/patterns/project-structure.md +264 -0
  17. package/LLMD/patterns/type-safety.md +440 -0
  18. package/LLMD/reference/.gitkeep +1 -0
  19. package/LLMD/reference/cli-commands.md +250 -0
  20. package/LLMD/reference/plugin-hooks.md +357 -0
  21. package/LLMD/reference/routing.md +39 -0
  22. package/LLMD/reference/troubleshooting.md +364 -0
  23. package/LLMD/resources/.gitkeep +1 -0
  24. package/LLMD/resources/controllers.md +465 -0
  25. package/LLMD/resources/live-components.md +703 -0
  26. package/LLMD/resources/live-rooms.md +482 -0
  27. package/LLMD/resources/live-upload.md +130 -0
  28. package/LLMD/resources/plugins-external.md +617 -0
  29. package/LLMD/resources/routes-eden.md +254 -0
  30. package/README.md +37 -17
  31. package/app/client/index.html +0 -1
  32. package/app/client/src/App.tsx +107 -150
  33. package/app/client/src/components/AppLayout.tsx +68 -0
  34. package/app/client/src/components/BackButton.tsx +13 -0
  35. package/app/client/src/components/DemoPage.tsx +20 -0
  36. package/app/client/src/components/LiveUploadWidget.tsx +204 -0
  37. package/app/client/src/lib/eden-api.ts +85 -60
  38. package/app/client/src/live/ChatDemo.tsx +107 -0
  39. package/app/client/src/live/CounterDemo.tsx +206 -0
  40. package/app/client/src/live/FormDemo.tsx +119 -0
  41. package/app/client/src/live/RoomChatDemo.tsx +242 -0
  42. package/app/client/src/live/UploadDemo.tsx +21 -0
  43. package/app/client/src/main.tsx +4 -1
  44. package/app/client/src/pages/ApiTestPage.tsx +108 -0
  45. package/app/client/src/pages/HomePage.tsx +76 -0
  46. package/app/server/app.ts +1 -4
  47. package/app/server/controllers/users.controller.ts +36 -44
  48. package/app/server/index.ts +25 -35
  49. package/app/server/live/LiveChat.ts +77 -0
  50. package/app/server/live/LiveCounter.ts +67 -0
  51. package/app/server/live/LiveForm.ts +63 -0
  52. package/app/server/live/LiveLocalCounter.ts +32 -0
  53. package/app/server/live/LiveRoomChat.ts +285 -0
  54. package/app/server/live/LiveUpload.ts +81 -0
  55. package/app/server/routes/index.ts +3 -1
  56. package/app/server/routes/room.routes.ts +117 -0
  57. package/app/server/routes/users.routes.ts +35 -27
  58. package/app/shared/types/index.ts +14 -2
  59. package/config/app.config.ts +2 -62
  60. package/config/client.config.ts +2 -95
  61. package/config/database.config.ts +2 -99
  62. package/config/fluxstack.config.ts +25 -45
  63. package/config/index.ts +57 -38
  64. package/config/monitoring.config.ts +2 -114
  65. package/config/plugins.config.ts +2 -80
  66. package/config/server.config.ts +2 -68
  67. package/config/services.config.ts +2 -130
  68. package/config/system/app.config.ts +29 -0
  69. package/config/system/build.config.ts +49 -0
  70. package/config/system/client.config.ts +68 -0
  71. package/config/system/database.config.ts +17 -0
  72. package/config/system/fluxstack.config.ts +114 -0
  73. package/config/{logger.config.ts → system/logger.config.ts} +3 -1
  74. package/config/system/monitoring.config.ts +114 -0
  75. package/config/system/plugins.config.ts +84 -0
  76. package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
  77. package/config/system/server.config.ts +68 -0
  78. package/config/system/services.config.ts +46 -0
  79. package/config/{system.config.ts → system/system.config.ts} +1 -1
  80. package/core/build/flux-plugins-generator.ts +325 -325
  81. package/core/build/index.ts +39 -27
  82. package/core/build/live-components-generator.ts +3 -3
  83. package/core/build/optimizer.ts +235 -235
  84. package/core/cli/command-registry.ts +6 -4
  85. package/core/cli/commands/build.ts +79 -0
  86. package/core/cli/commands/create.ts +54 -0
  87. package/core/cli/commands/dev.ts +101 -0
  88. package/core/cli/commands/help.ts +34 -0
  89. package/core/cli/commands/index.ts +34 -0
  90. package/core/cli/commands/make-plugin.ts +90 -0
  91. package/core/cli/commands/plugin-add.ts +197 -0
  92. package/core/cli/commands/plugin-deps.ts +2 -2
  93. package/core/cli/commands/plugin-list.ts +208 -0
  94. package/core/cli/commands/plugin-remove.ts +170 -0
  95. package/core/cli/generators/component.ts +769 -769
  96. package/core/cli/generators/controller.ts +1 -1
  97. package/core/cli/generators/index.ts +146 -146
  98. package/core/cli/generators/interactive.ts +227 -227
  99. package/core/cli/generators/plugin.ts +2 -2
  100. package/core/cli/generators/prompts.ts +82 -82
  101. package/core/cli/generators/route.ts +6 -6
  102. package/core/cli/generators/service.ts +2 -2
  103. package/core/cli/generators/template-engine.ts +4 -3
  104. package/core/cli/generators/types.ts +2 -2
  105. package/core/cli/generators/utils.ts +191 -191
  106. package/core/cli/index.ts +115 -686
  107. package/core/cli/plugin-discovery.ts +2 -2
  108. package/core/client/LiveComponentsProvider.tsx +60 -8
  109. package/core/client/api/eden.ts +183 -0
  110. package/core/client/api/index.ts +11 -0
  111. package/core/client/components/Live.tsx +104 -0
  112. package/core/client/fluxstack.ts +1 -9
  113. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
  114. package/core/client/hooks/state-validator.ts +1 -1
  115. package/core/client/hooks/useAuth.ts +48 -48
  116. package/core/client/hooks/useChunkedUpload.ts +85 -35
  117. package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
  118. package/core/client/hooks/useLiveComponent.ts +800 -0
  119. package/core/client/hooks/useLiveUpload.ts +71 -0
  120. package/core/client/hooks/useRoom.ts +409 -0
  121. package/core/client/hooks/useRoomProxy.ts +382 -0
  122. package/core/client/index.ts +17 -68
  123. package/core/client/standalone-entry.ts +8 -0
  124. package/core/client/standalone.ts +74 -53
  125. package/core/client/state/createStore.ts +192 -192
  126. package/core/client/state/index.ts +14 -14
  127. package/core/config/index.ts +70 -291
  128. package/core/config/schema.ts +42 -723
  129. package/core/framework/client.ts +131 -131
  130. package/core/framework/index.ts +7 -7
  131. package/core/framework/server.ts +47 -40
  132. package/core/framework/types.ts +2 -2
  133. package/core/index.ts +23 -4
  134. package/core/live/ComponentRegistry.ts +3 -3
  135. package/core/live/types.ts +77 -0
  136. package/core/plugins/built-in/index.ts +134 -134
  137. package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1066
  138. package/core/plugins/built-in/live-components/index.ts +1 -1
  139. package/core/plugins/built-in/monitoring/index.ts +111 -47
  140. package/core/plugins/built-in/static/index.ts +1 -1
  141. package/core/plugins/built-in/swagger/index.ts +68 -265
  142. package/core/plugins/built-in/vite/index.ts +85 -185
  143. package/core/plugins/built-in/vite/vite-dev.ts +10 -16
  144. package/core/plugins/config.ts +9 -7
  145. package/core/plugins/dependency-manager.ts +31 -1
  146. package/core/plugins/discovery.ts +19 -7
  147. package/core/plugins/executor.ts +2 -2
  148. package/core/plugins/index.ts +203 -203
  149. package/core/plugins/manager.ts +27 -39
  150. package/core/plugins/module-resolver.ts +19 -8
  151. package/core/plugins/registry.ts +255 -19
  152. package/core/plugins/types.ts +20 -53
  153. package/core/server/framework.ts +66 -43
  154. package/core/server/index.ts +15 -15
  155. package/core/server/live/ComponentRegistry.ts +78 -71
  156. package/core/server/live/FileUploadManager.ts +23 -10
  157. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  158. package/core/server/live/LiveRoomManager.ts +261 -0
  159. package/core/server/live/RoomEventBus.ts +234 -0
  160. package/core/server/live/RoomStateManager.ts +172 -0
  161. package/core/server/live/StateSignature.ts +643 -643
  162. package/core/server/live/WebSocketConnectionManager.ts +30 -19
  163. package/core/server/live/auto-generated-components.ts +21 -9
  164. package/core/server/live/index.ts +14 -0
  165. package/core/server/live/websocket-plugin.ts +214 -67
  166. package/core/server/middleware/elysia-helpers.ts +7 -2
  167. package/core/server/middleware/errorHandling.ts +1 -1
  168. package/core/server/middleware/index.ts +31 -31
  169. package/core/server/plugins/database.ts +180 -180
  170. package/core/server/plugins/static-files-plugin.ts +69 -69
  171. package/core/server/plugins/swagger.ts +1 -1
  172. package/core/server/rooms/RoomBroadcaster.ts +357 -0
  173. package/core/server/rooms/RoomSystem.ts +463 -0
  174. package/core/server/rooms/index.ts +13 -0
  175. package/core/server/services/BaseService.ts +1 -1
  176. package/core/server/services/ServiceContainer.ts +1 -1
  177. package/core/server/services/index.ts +8 -8
  178. package/core/templates/create-project.ts +12 -12
  179. package/core/testing/index.ts +9 -9
  180. package/core/testing/setup.ts +73 -73
  181. package/core/types/api.ts +168 -168
  182. package/core/types/build.ts +219 -219
  183. package/core/types/config.ts +56 -26
  184. package/core/types/index.ts +4 -4
  185. package/core/types/plugin.ts +107 -107
  186. package/core/types/types.ts +353 -14
  187. package/core/utils/build-logger.ts +324 -324
  188. package/core/utils/config-schema.ts +480 -480
  189. package/core/utils/env.ts +2 -8
  190. package/core/utils/errors/codes.ts +114 -114
  191. package/core/utils/errors/handlers.ts +36 -1
  192. package/core/utils/errors/index.ts +49 -5
  193. package/core/utils/errors/middleware.ts +113 -113
  194. package/core/utils/helpers.ts +6 -16
  195. package/core/utils/index.ts +17 -17
  196. package/core/utils/logger/colors.ts +114 -114
  197. package/core/utils/logger/config.ts +13 -9
  198. package/core/utils/logger/formatter.ts +82 -82
  199. package/core/utils/logger/group-logger.ts +101 -101
  200. package/core/utils/logger/index.ts +6 -1
  201. package/core/utils/logger/stack-trace.ts +3 -1
  202. package/core/utils/logger/startup-banner.ts +82 -82
  203. package/core/utils/logger/winston-logger.ts +152 -152
  204. package/core/utils/monitoring/index.ts +211 -211
  205. package/core/utils/sync-version.ts +66 -66
  206. package/core/utils/version.ts +1 -1
  207. package/create-fluxstack.ts +8 -7
  208. package/package.json +12 -13
  209. package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
  210. package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
  211. package/plugins/crypto-auth/client/components/index.ts +11 -11
  212. package/plugins/crypto-auth/client/index.ts +11 -11
  213. package/plugins/crypto-auth/config/index.ts +1 -1
  214. package/plugins/crypto-auth/index.ts +4 -4
  215. package/plugins/crypto-auth/package.json +65 -65
  216. package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
  217. package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
  218. package/plugins/crypto-auth/server/index.ts +21 -21
  219. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
  220. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
  221. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
  222. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
  223. package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
  224. package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
  225. package/tsconfig.api-strict.json +16 -0
  226. package/tsconfig.json +48 -52
  227. package/{app/client/tsconfig.node.json → tsconfig.node.json} +25 -25
  228. package/types/global.d.ts +29 -29
  229. package/types/vitest.d.ts +8 -8
  230. package/vite.config.ts +38 -62
  231. package/vitest.config.live.ts +10 -9
  232. package/vitest.config.ts +29 -17
  233. package/app/client/README.md +0 -69
  234. package/app/client/SIMPLIFICATION.md +0 -140
  235. package/app/client/frontend-only.ts +0 -12
  236. package/app/client/src/live/FileUploadExample.tsx +0 -359
  237. package/app/client/src/live/MinimalLiveClock.tsx +0 -47
  238. package/app/client/src/live/QuickUploadTest.tsx +0 -193
  239. package/app/client/tsconfig.app.json +0 -45
  240. package/app/client/tsconfig.json +0 -7
  241. package/app/client/zustand-setup.md +0 -65
  242. package/app/server/backend-only.ts +0 -18
  243. package/app/server/live/LiveClockComponent.ts +0 -215
  244. package/app/server/live/LiveFileUploadComponent.ts +0 -77
  245. package/app/server/routes/env-test.ts +0 -110
  246. package/core/client/hooks/index.ts +0 -7
  247. package/core/client/hooks/useHybridLiveComponent.ts +0 -685
  248. package/core/client/hooks/useTypedLiveComponent.ts +0 -133
  249. package/core/client/hooks/useWebSocket.ts +0 -361
  250. package/core/config/env.ts +0 -546
  251. package/core/config/loader.ts +0 -522
  252. package/core/config/runtime-config.ts +0 -327
  253. package/core/config/validator.ts +0 -540
  254. package/core/server/backend-entry.ts +0 -51
  255. package/core/server/standalone.ts +0 -106
  256. package/core/utils/regenerate-files.ts +0 -69
  257. package/fluxstack.config.ts +0 -354
@@ -1,133 +0,0 @@
1
- // 🔥 Typed Live Component Hook - Full Type Inference for Actions
2
- // Similar to Eden Treaty - automatic type inference from backend components
3
-
4
- import { useHybridLiveComponent } from './useHybridLiveComponent'
5
- import type { UseHybridLiveComponentReturn } from './useHybridLiveComponent'
6
- import type {
7
- LiveComponent,
8
- InferComponentState,
9
- HybridComponentOptions,
10
- UseTypedLiveComponentReturn,
11
- ActionNames,
12
- ActionPayload,
13
- ActionReturn
14
- } from '@/core/types/types'
15
-
16
- /**
17
- * Type-safe Live Component hook with automatic action inference
18
- *
19
- * @example
20
- * // Backend component definition
21
- * class LiveClockComponent extends LiveComponent<LiveClockState> {
22
- * async setTimeFormat(payload: { format: '12h' | '24h' }) { ... }
23
- * async toggleSeconds(payload?: { showSeconds?: boolean }) { ... }
24
- * async getServerInfo() { ... }
25
- * }
26
- *
27
- * // Frontend usage with full type inference
28
- * const { state, call, callAndWait } = useTypedLiveComponent<LiveClockComponent>(
29
- * 'LiveClock',
30
- * initialState
31
- * )
32
- *
33
- * // ✅ Autocomplete for action names
34
- * await call('setTimeFormat', { format: '12h' })
35
- *
36
- * // ✅ Type error if wrong payload
37
- * await call('setTimeFormat', { format: 'invalid' }) // Error!
38
- *
39
- * // ✅ Return type is inferred
40
- * const result = await callAndWait('getServerInfo')
41
- * // result is: { success: boolean; info: { serverTime: string; ... } }
42
- */
43
- export function useTypedLiveComponent<T extends LiveComponent<any>>(
44
- componentName: string,
45
- initialState: InferComponentState<T>,
46
- options: HybridComponentOptions = {}
47
- ): UseTypedLiveComponentReturn<T> {
48
- // Use the original hook
49
- const result = useHybridLiveComponent<InferComponentState<T>>(
50
- componentName,
51
- initialState,
52
- options
53
- )
54
-
55
- // Create convenience setValue helper
56
- const setValue = async <K extends keyof InferComponentState<T>>(
57
- key: K,
58
- value: InferComponentState<T>[K]
59
- ): Promise<void> => {
60
- await result.call('setValue', { key, value })
61
- }
62
-
63
- // Return with typed call functions and setValue helper
64
- // The types are enforced at compile time, runtime behavior is the same
65
- return {
66
- ...result,
67
- setValue
68
- } as unknown as UseTypedLiveComponentReturn<T>
69
- }
70
-
71
- /**
72
- * Helper type to create a component registry map
73
- * Maps component names to their class types for even better DX
74
- *
75
- * @example
76
- * // Define your component map
77
- * type MyComponents = {
78
- * LiveClock: LiveClockComponent
79
- * LiveCounter: LiveCounterComponent
80
- * LiveChat: LiveChatComponent
81
- * }
82
- *
83
- * // Create a typed hook for your app
84
- * function useMyComponent<K extends keyof MyComponents>(
85
- * name: K,
86
- * initialState: ComponentState<MyComponents[K]>,
87
- * options?: HybridComponentOptions
88
- * ) {
89
- * return useTypedLiveComponent<MyComponents[K]>(name, initialState, options)
90
- * }
91
- *
92
- * // Usage
93
- * const clock = useMyComponent('LiveClock', { ... })
94
- * // TypeScript knows exactly which actions are available!
95
- */
96
- export type ComponentRegistry<T extends Record<string, LiveComponent<any>>> = {
97
- [K in keyof T]: T[K]
98
- }
99
-
100
- /**
101
- * Create a factory for typed live component hooks
102
- * Useful when you have many components and want simpler imports
103
- *
104
- * @example
105
- * // In your app/client/src/lib/live.ts
106
- * import { createTypedLiveComponentHook } from '@/core/client/hooks/useTypedLiveComponent'
107
- * import type { LiveClockComponent } from '@/app/server/live/LiveClockComponent'
108
- *
109
- * export const useLiveClock = createTypedLiveComponentHook<LiveClockComponent>('LiveClock')
110
- *
111
- * // Usage in component
112
- * const { state, call } = useLiveClock({ currentTime: '', ... })
113
- */
114
- export function createTypedLiveComponentHook<T extends LiveComponent<any>>(
115
- componentName: string
116
- ) {
117
- return function useComponent(
118
- initialState: InferComponentState<T>,
119
- options: HybridComponentOptions = {}
120
- ): UseTypedLiveComponentReturn<T> {
121
- return useTypedLiveComponent<T>(componentName, initialState, options)
122
- }
123
- }
124
-
125
- // Re-export types for convenience
126
- export type {
127
- InferComponentState,
128
- ActionNames,
129
- ActionPayload,
130
- ActionReturn,
131
- UseTypedLiveComponentReturn,
132
- HybridComponentOptions
133
- }
@@ -1,361 +0,0 @@
1
- // 🔥 WebSocket Hook for Live Components
2
-
3
- import { useState, useEffect, useCallback, useRef } from 'react'
4
- import type { WebSocketMessage, WebSocketResponse } from '@/core/types/types'
5
-
6
- // Re-export types for easier importing
7
- export type { WebSocketMessage, WebSocketResponse }
8
-
9
- export interface UseWebSocketOptions {
10
- url?: string
11
- autoConnect?: boolean
12
- reconnectInterval?: number
13
- maxReconnectAttempts?: number
14
- debug?: boolean
15
- }
16
-
17
- export interface UseWebSocketReturn {
18
- connected: boolean
19
- connecting: boolean
20
- error: string | null
21
- connectionId: string | null
22
- sendMessage: (message: WebSocketMessage) => Promise<WebSocketResponse | null>
23
- sendMessageAndWait: (message: WebSocketMessage, timeout?: number) => Promise<any>
24
- close: () => void
25
- reconnect: () => void
26
- messageHistory: WebSocketResponse[]
27
- lastMessage: WebSocketResponse | null
28
- onMessage: (callback: (message: WebSocketResponse) => void) => () => void
29
- }
30
-
31
- export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketReturn {
32
- // Get WebSocket URL dynamically based on current environment
33
- const getWebSocketUrl = () => {
34
- if (typeof window === 'undefined') return 'ws://localhost:3000/api/live/ws'
35
-
36
- // Always use current host - works for both dev and production
37
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
38
- return `${protocol}//${window.location.host}/api/live/ws`
39
- }
40
-
41
- const {
42
- url = getWebSocketUrl(),
43
- autoConnect = true,
44
- reconnectInterval = 300, // Reduced from 3000ms to 1000ms for faster reconnects
45
- maxReconnectAttempts = 5,
46
- debug = false
47
- } = options
48
-
49
- const [connected, setConnected] = useState(false)
50
- const [connecting, setConnecting] = useState(false)
51
- const [error, setError] = useState<string | null>(null)
52
- const [connectionId, setConnectionId] = useState<string | null>(null)
53
- const [messageHistory, setMessageHistory] = useState<WebSocketResponse[]>([])
54
- const [lastMessage, setLastMessage] = useState<WebSocketResponse | null>(null)
55
-
56
- // Request-Response system
57
- const pendingRequests = useRef<Map<string, {
58
- resolve: (value: any) => void
59
- reject: (error: any) => void
60
- timeout: NodeJS.Timeout
61
- }>>(new Map())
62
-
63
- // Message callbacks for real-time processing
64
- const messageCallbacksRef = useRef<Set<(message: WebSocketResponse) => void>>(new Set())
65
-
66
- // Generate unique request ID
67
- const generateRequestId = useCallback(() => {
68
- return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
69
- }, [])
70
-
71
- const wsRef = useRef<WebSocket | null>(null)
72
- const reconnectAttempts = useRef(0)
73
- const reconnectTimeout = useRef<number | null>(null)
74
- const messageCallbacks = useRef<Map<string, (response: WebSocketResponse) => void>>(new Map())
75
-
76
- const log = useCallback((message: string, data?: any) => {
77
- if (debug) {
78
- console.log(`[useWebSocket] ${message}`, data)
79
- }
80
- }, [debug])
81
-
82
- const connect = useCallback(() => {
83
- if (wsRef.current?.readyState === WebSocket.CONNECTING) {
84
- log('WebSocket already connecting, skipping...')
85
- return
86
- }
87
-
88
- if (wsRef.current?.readyState === WebSocket.OPEN) {
89
- log('WebSocket already connected, skipping...')
90
- return
91
- }
92
-
93
- setConnecting(true)
94
- setError(null)
95
- log('Connecting to WebSocket', { url })
96
-
97
- try {
98
- const ws = new WebSocket(url)
99
- wsRef.current = ws
100
-
101
- ws.onopen = () => {
102
- setConnected(true)
103
- setConnecting(false)
104
- reconnectAttempts.current = 0
105
- log('Connected to WebSocket')
106
- }
107
-
108
- ws.onmessage = (event) => {
109
- try {
110
- const response: WebSocketResponse = JSON.parse(event.data)
111
- log('Received message', response)
112
-
113
- // Handle connection establishment
114
- if (response.type === 'CONNECTION_ESTABLISHED') {
115
- setConnectionId(response.connectionId || null)
116
- }
117
-
118
- // Handle request-response system
119
- if (response.requestId && pendingRequests.current.has(response.requestId)) {
120
- const request = pendingRequests.current.get(response.requestId)!
121
- clearTimeout(request.timeout)
122
- pendingRequests.current.delete(response.requestId)
123
-
124
- if (response.success !== false) {
125
- request.resolve(response) // Pass full response, not just result
126
- } else {
127
- // Don't reject COMPONENT_REHYDRATION_REQUIRED - let client handle it
128
- if (response.error?.includes?.('COMPONENT_REHYDRATION_REQUIRED')) {
129
- request.resolve(response) // Return response so client can handle re-hydration
130
- } else {
131
- request.reject(new Error(response.error || 'Request failed'))
132
- }
133
- }
134
- return // Don't process further for request-response
135
- }
136
-
137
- // Handle message callbacks (legacy)
138
- if (response.type === 'MESSAGE_RESPONSE' && response.componentId) {
139
- const callback = messageCallbacks.current.get(response.componentId)
140
- if (callback) {
141
- callback(response)
142
- messageCallbacks.current.delete(response.componentId)
143
- }
144
- }
145
-
146
- // Update message history and last message
147
- setMessageHistory(prev => [...prev.slice(-99), response])
148
- setLastMessage(response)
149
-
150
- // Call all registered message callbacks immediately
151
- messageCallbacksRef.current.forEach(callback => {
152
- try {
153
- callback(response)
154
- } catch (error) {
155
- log('Error in message callback', error)
156
- }
157
- })
158
-
159
- } catch (error) {
160
- log('Failed to parse WebSocket message', error)
161
- setError('Failed to parse message')
162
- }
163
- }
164
-
165
- ws.onclose = () => {
166
- setConnected(false)
167
- setConnecting(false)
168
- setConnectionId(null)
169
- log('WebSocket connection closed')
170
-
171
- // Auto-reconnect logic
172
- if (reconnectAttempts.current < maxReconnectAttempts) {
173
- reconnectAttempts.current++
174
- log(`Attempting to reconnect (${reconnectAttempts.current}/${maxReconnectAttempts})`)
175
-
176
- reconnectTimeout.current = window.setTimeout(() => {
177
- connect()
178
- }, reconnectInterval)
179
- } else {
180
- setError('Max reconnection attempts reached')
181
- }
182
- }
183
-
184
- ws.onerror = (error) => {
185
- log('WebSocket error', error)
186
- setError('WebSocket connection error')
187
- setConnecting(false)
188
- }
189
-
190
- } catch (error) {
191
- setConnecting(false)
192
- setError(error instanceof Error ? error.message : 'Connection failed')
193
- log('Failed to create WebSocket connection', error)
194
- }
195
- }, [url, reconnectInterval, maxReconnectAttempts, log])
196
-
197
- const sendMessage = useCallback(async (message: WebSocketMessage): Promise<WebSocketResponse | null> => {
198
- return new Promise((resolve, reject) => {
199
- if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
200
- reject(new Error('WebSocket is not connected'))
201
- return
202
- }
203
-
204
- // CALL_ACTION doesn't expect response - send and resolve immediately
205
- if (message.type === 'CALL_ACTION') {
206
- try {
207
- const messageWithTimestamp = { ...message, timestamp: Date.now() }
208
- wsRef.current.send(JSON.stringify(messageWithTimestamp))
209
- log('Sent message', messageWithTimestamp)
210
- resolve(null) // No response expected
211
- return
212
- } catch (error) {
213
- reject(error)
214
- return
215
- }
216
- }
217
-
218
- // Generate unique message ID for response tracking (other message types)
219
- const messageId = `${message.componentId || 'msg'}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
220
-
221
- // Set up callback for response
222
- if (message.componentId) {
223
- messageCallbacks.current.set(message.componentId, (response) => {
224
- resolve(response)
225
- })
226
-
227
- // Timeout after 10 seconds
228
- setTimeout(() => {
229
- if (messageCallbacks.current.has(message.componentId!)) {
230
- messageCallbacks.current.delete(message.componentId!)
231
- reject(new Error('Message timeout'))
232
- }
233
- }, 10000)
234
- }
235
-
236
- try {
237
- const messageWithTimestamp = {
238
- ...message,
239
- timestamp: Date.now()
240
- }
241
-
242
- wsRef.current.send(JSON.stringify(messageWithTimestamp))
243
- log('Sent message', messageWithTimestamp)
244
-
245
- // If no component ID, resolve immediately
246
- if (!message.componentId) {
247
- resolve(null)
248
- }
249
- } catch (error) {
250
- if (message.componentId) {
251
- messageCallbacks.current.delete(message.componentId)
252
- }
253
- reject(error)
254
- }
255
- })
256
- }, [log])
257
-
258
- // Send message and wait for response with unique ID
259
- const sendMessageAndWait = useCallback(async (
260
- message: WebSocketMessage,
261
- timeout: number = 10000
262
- ): Promise<any> => {
263
- return new Promise((resolve, reject) => {
264
- if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
265
- reject(new Error('WebSocket is not connected'))
266
- return
267
- }
268
-
269
- const requestId = generateRequestId()
270
-
271
- // Set up timeout
272
- const timeoutHandle = setTimeout(() => {
273
- pendingRequests.current.delete(requestId)
274
- reject(new Error(`Request timeout after ${timeout}ms`))
275
- }, timeout)
276
-
277
- // Store the pending request
278
- pendingRequests.current.set(requestId, {
279
- resolve,
280
- reject,
281
- timeout: timeoutHandle
282
- })
283
-
284
- try {
285
- const messageWithRequestId = {
286
- ...message,
287
- requestId,
288
- expectResponse: true,
289
- timestamp: Date.now()
290
- }
291
-
292
- wsRef.current.send(JSON.stringify(messageWithRequestId))
293
- log('Sent message with request ID', { requestId, message: messageWithRequestId })
294
- } catch (error) {
295
- // Cleanup on send error
296
- clearTimeout(timeoutHandle)
297
- pendingRequests.current.delete(requestId)
298
- reject(error)
299
- }
300
- })
301
- }, [log, generateRequestId])
302
-
303
- const close = useCallback(() => {
304
- if (reconnectTimeout.current) {
305
- clearTimeout(reconnectTimeout.current)
306
- reconnectTimeout.current = null
307
- }
308
-
309
- if (wsRef.current) {
310
- wsRef.current.close()
311
- wsRef.current = null
312
- }
313
-
314
- reconnectAttempts.current = maxReconnectAttempts // Prevent auto-reconnect
315
- setConnected(false)
316
- setConnecting(false)
317
- setConnectionId(null)
318
- log('WebSocket connection closed manually')
319
- }, [maxReconnectAttempts, log])
320
-
321
- const reconnect = useCallback(() => {
322
- close()
323
- reconnectAttempts.current = 0
324
- setTimeout(connect, 50) // Reduced delay from 100ms to 50ms
325
- }, [close, connect])
326
-
327
- // Auto-connect on mount
328
- useEffect(() => {
329
- if (autoConnect) {
330
- connect()
331
- }
332
-
333
- return () => {
334
- close()
335
- }
336
- }, [autoConnect, connect, close])
337
-
338
- // Register message callback
339
- const onMessage = useCallback((callback: (message: WebSocketResponse) => void) => {
340
- messageCallbacksRef.current.add(callback)
341
-
342
- // Return cleanup function
343
- return () => {
344
- messageCallbacksRef.current.delete(callback)
345
- }
346
- }, [])
347
-
348
- return {
349
- connected,
350
- connecting,
351
- error,
352
- connectionId,
353
- sendMessage,
354
- sendMessageAndWait,
355
- close,
356
- reconnect,
357
- messageHistory,
358
- lastMessage,
359
- onMessage
360
- }
361
- }