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.
- package/.dockerignore +1 -2
- package/Dockerfile +8 -8
- package/LLMD/INDEX.md +64 -0
- package/LLMD/MAINTENANCE.md +197 -0
- package/LLMD/MIGRATION.md +156 -0
- package/LLMD/config/.gitkeep +1 -0
- package/LLMD/config/declarative-system.md +268 -0
- package/LLMD/config/environment-vars.md +327 -0
- package/LLMD/config/runtime-reload.md +401 -0
- package/LLMD/core/.gitkeep +1 -0
- package/LLMD/core/build-system.md +599 -0
- package/LLMD/core/framework-lifecycle.md +229 -0
- package/LLMD/core/plugin-system.md +451 -0
- package/LLMD/patterns/.gitkeep +1 -0
- package/LLMD/patterns/anti-patterns.md +297 -0
- package/LLMD/patterns/project-structure.md +264 -0
- package/LLMD/patterns/type-safety.md +440 -0
- package/LLMD/reference/.gitkeep +1 -0
- package/LLMD/reference/cli-commands.md +250 -0
- package/LLMD/reference/plugin-hooks.md +357 -0
- package/LLMD/reference/routing.md +39 -0
- package/LLMD/reference/troubleshooting.md +364 -0
- package/LLMD/resources/.gitkeep +1 -0
- package/LLMD/resources/controllers.md +465 -0
- package/LLMD/resources/live-components.md +703 -0
- package/LLMD/resources/live-rooms.md +482 -0
- package/LLMD/resources/live-upload.md +130 -0
- package/LLMD/resources/plugins-external.md +617 -0
- package/LLMD/resources/routes-eden.md +254 -0
- package/README.md +37 -17
- package/app/client/index.html +0 -1
- package/app/client/src/App.tsx +107 -150
- package/app/client/src/components/AppLayout.tsx +68 -0
- package/app/client/src/components/BackButton.tsx +13 -0
- package/app/client/src/components/DemoPage.tsx +20 -0
- package/app/client/src/components/LiveUploadWidget.tsx +204 -0
- package/app/client/src/lib/eden-api.ts +85 -60
- package/app/client/src/live/ChatDemo.tsx +107 -0
- package/app/client/src/live/CounterDemo.tsx +206 -0
- package/app/client/src/live/FormDemo.tsx +119 -0
- package/app/client/src/live/RoomChatDemo.tsx +242 -0
- package/app/client/src/live/UploadDemo.tsx +21 -0
- package/app/client/src/main.tsx +4 -1
- package/app/client/src/pages/ApiTestPage.tsx +108 -0
- package/app/client/src/pages/HomePage.tsx +76 -0
- package/app/server/app.ts +1 -4
- package/app/server/controllers/users.controller.ts +36 -44
- package/app/server/index.ts +25 -35
- package/app/server/live/LiveChat.ts +77 -0
- package/app/server/live/LiveCounter.ts +67 -0
- package/app/server/live/LiveForm.ts +63 -0
- package/app/server/live/LiveLocalCounter.ts +32 -0
- package/app/server/live/LiveRoomChat.ts +285 -0
- package/app/server/live/LiveUpload.ts +81 -0
- package/app/server/routes/index.ts +3 -1
- package/app/server/routes/room.routes.ts +117 -0
- package/app/server/routes/users.routes.ts +35 -27
- package/app/shared/types/index.ts +14 -2
- package/config/app.config.ts +2 -62
- package/config/client.config.ts +2 -95
- package/config/database.config.ts +2 -99
- package/config/fluxstack.config.ts +25 -45
- package/config/index.ts +57 -38
- package/config/monitoring.config.ts +2 -114
- package/config/plugins.config.ts +2 -80
- package/config/server.config.ts +2 -68
- package/config/services.config.ts +2 -130
- package/config/system/app.config.ts +29 -0
- package/config/system/build.config.ts +49 -0
- package/config/system/client.config.ts +68 -0
- package/config/system/database.config.ts +17 -0
- package/config/system/fluxstack.config.ts +114 -0
- package/config/{logger.config.ts → system/logger.config.ts} +3 -1
- package/config/system/monitoring.config.ts +114 -0
- package/config/system/plugins.config.ts +84 -0
- package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
- package/config/system/server.config.ts +68 -0
- package/config/system/services.config.ts +46 -0
- package/config/{system.config.ts → system/system.config.ts} +1 -1
- package/core/build/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +39 -27
- package/core/build/live-components-generator.ts +3 -3
- package/core/build/optimizer.ts +235 -235
- package/core/cli/command-registry.ts +6 -4
- package/core/cli/commands/build.ts +79 -0
- package/core/cli/commands/create.ts +54 -0
- package/core/cli/commands/dev.ts +101 -0
- package/core/cli/commands/help.ts +34 -0
- package/core/cli/commands/index.ts +34 -0
- package/core/cli/commands/make-plugin.ts +90 -0
- package/core/cli/commands/plugin-add.ts +197 -0
- package/core/cli/commands/plugin-deps.ts +2 -2
- package/core/cli/commands/plugin-list.ts +208 -0
- package/core/cli/commands/plugin-remove.ts +170 -0
- package/core/cli/generators/component.ts +769 -769
- package/core/cli/generators/controller.ts +1 -1
- package/core/cli/generators/index.ts +146 -146
- package/core/cli/generators/interactive.ts +227 -227
- package/core/cli/generators/plugin.ts +2 -2
- package/core/cli/generators/prompts.ts +82 -82
- package/core/cli/generators/route.ts +6 -6
- package/core/cli/generators/service.ts +2 -2
- package/core/cli/generators/template-engine.ts +4 -3
- package/core/cli/generators/types.ts +2 -2
- package/core/cli/generators/utils.ts +191 -191
- package/core/cli/index.ts +115 -686
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/LiveComponentsProvider.tsx +60 -8
- package/core/client/api/eden.ts +183 -0
- package/core/client/api/index.ts +11 -0
- package/core/client/components/Live.tsx +104 -0
- package/core/client/fluxstack.ts +1 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/hooks/useChunkedUpload.ts +85 -35
- package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
- package/core/client/hooks/useLiveComponent.ts +800 -0
- package/core/client/hooks/useLiveUpload.ts +71 -0
- package/core/client/hooks/useRoom.ts +409 -0
- package/core/client/hooks/useRoomProxy.ts +382 -0
- package/core/client/index.ts +17 -68
- package/core/client/standalone-entry.ts +8 -0
- package/core/client/standalone.ts +74 -53
- package/core/client/state/createStore.ts +192 -192
- package/core/client/state/index.ts +14 -14
- package/core/config/index.ts +70 -291
- package/core/config/schema.ts +42 -723
- package/core/framework/client.ts +131 -131
- package/core/framework/index.ts +7 -7
- package/core/framework/server.ts +47 -40
- package/core/framework/types.ts +2 -2
- package/core/index.ts +23 -4
- package/core/live/ComponentRegistry.ts +3 -3
- package/core/live/types.ts +77 -0
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1066
- package/core/plugins/built-in/live-components/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +111 -47
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +68 -265
- package/core/plugins/built-in/vite/index.ts +85 -185
- package/core/plugins/built-in/vite/vite-dev.ts +10 -16
- package/core/plugins/config.ts +9 -7
- package/core/plugins/dependency-manager.ts +31 -1
- package/core/plugins/discovery.ts +19 -7
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/index.ts +203 -203
- package/core/plugins/manager.ts +27 -39
- package/core/plugins/module-resolver.ts +19 -8
- package/core/plugins/registry.ts +255 -19
- package/core/plugins/types.ts +20 -53
- package/core/server/framework.ts +66 -43
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +78 -71
- package/core/server/live/FileUploadManager.ts +23 -10
- package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
- package/core/server/live/LiveRoomManager.ts +261 -0
- package/core/server/live/RoomEventBus.ts +234 -0
- package/core/server/live/RoomStateManager.ts +172 -0
- package/core/server/live/StateSignature.ts +643 -643
- package/core/server/live/WebSocketConnectionManager.ts +30 -19
- package/core/server/live/auto-generated-components.ts +21 -9
- package/core/server/live/index.ts +14 -0
- package/core/server/live/websocket-plugin.ts +214 -67
- package/core/server/middleware/elysia-helpers.ts +7 -2
- package/core/server/middleware/errorHandling.ts +1 -1
- package/core/server/middleware/index.ts +31 -31
- package/core/server/plugins/database.ts +180 -180
- package/core/server/plugins/static-files-plugin.ts +69 -69
- package/core/server/plugins/swagger.ts +1 -1
- package/core/server/rooms/RoomBroadcaster.ts +357 -0
- package/core/server/rooms/RoomSystem.ts +463 -0
- package/core/server/rooms/index.ts +13 -0
- package/core/server/services/BaseService.ts +1 -1
- package/core/server/services/ServiceContainer.ts +1 -1
- package/core/server/services/index.ts +8 -8
- package/core/templates/create-project.ts +12 -12
- package/core/testing/index.ts +9 -9
- package/core/testing/setup.ts +73 -73
- package/core/types/api.ts +168 -168
- package/core/types/build.ts +219 -219
- package/core/types/config.ts +56 -26
- package/core/types/index.ts +4 -4
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +353 -14
- package/core/utils/build-logger.ts +324 -324
- package/core/utils/config-schema.ts +480 -480
- package/core/utils/env.ts +2 -8
- package/core/utils/errors/codes.ts +114 -114
- package/core/utils/errors/handlers.ts +36 -1
- package/core/utils/errors/index.ts +49 -5
- package/core/utils/errors/middleware.ts +113 -113
- package/core/utils/helpers.ts +6 -16
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/colors.ts +114 -114
- package/core/utils/logger/config.ts +13 -9
- package/core/utils/logger/formatter.ts +82 -82
- package/core/utils/logger/group-logger.ts +101 -101
- package/core/utils/logger/index.ts +6 -1
- package/core/utils/logger/stack-trace.ts +3 -1
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/logger/winston-logger.ts +152 -152
- package/core/utils/monitoring/index.ts +211 -211
- package/core/utils/sync-version.ts +66 -66
- package/core/utils/version.ts +1 -1
- package/create-fluxstack.ts +8 -7
- package/package.json +12 -13
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
- package/plugins/crypto-auth/client/components/index.ts +11 -11
- package/plugins/crypto-auth/client/index.ts +11 -11
- package/plugins/crypto-auth/config/index.ts +1 -1
- package/plugins/crypto-auth/index.ts +4 -4
- package/plugins/crypto-auth/package.json +65 -65
- package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
- package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
- package/plugins/crypto-auth/server/index.ts +21 -21
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
- package/tsconfig.api-strict.json +16 -0
- package/tsconfig.json +48 -52
- package/{app/client/tsconfig.node.json → tsconfig.node.json} +25 -25
- package/types/global.d.ts +29 -29
- package/types/vitest.d.ts +8 -8
- package/vite.config.ts +38 -62
- package/vitest.config.live.ts +10 -9
- package/vitest.config.ts +29 -17
- package/app/client/README.md +0 -69
- package/app/client/SIMPLIFICATION.md +0 -140
- package/app/client/frontend-only.ts +0 -12
- package/app/client/src/live/FileUploadExample.tsx +0 -359
- package/app/client/src/live/MinimalLiveClock.tsx +0 -47
- package/app/client/src/live/QuickUploadTest.tsx +0 -193
- package/app/client/tsconfig.app.json +0 -45
- package/app/client/tsconfig.json +0 -7
- package/app/client/zustand-setup.md +0 -65
- package/app/server/backend-only.ts +0 -18
- package/app/server/live/LiveClockComponent.ts +0 -215
- package/app/server/live/LiveFileUploadComponent.ts +0 -77
- package/app/server/routes/env-test.ts +0 -110
- package/core/client/hooks/index.ts +0 -7
- package/core/client/hooks/useHybridLiveComponent.ts +0 -685
- package/core/client/hooks/useTypedLiveComponent.ts +0 -133
- package/core/client/hooks/useWebSocket.ts +0 -361
- package/core/config/env.ts +0 -546
- package/core/config/loader.ts +0 -522
- package/core/config/runtime-config.ts +0 -327
- package/core/config/validator.ts +0 -540
- package/core/server/backend-entry.ts +0 -51
- package/core/server/standalone.ts +0 -106
- package/core/utils/regenerate-files.ts +0 -69
- 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
|
-
}
|