create-fluxstack 1.12.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LLMD/INDEX.md +8 -1
- package/LLMD/agent.md +867 -0
- package/LLMD/config/environment-vars.md +30 -0
- package/LLMD/patterns/anti-patterns.md +100 -0
- package/LLMD/reference/routing.md +39 -39
- package/LLMD/resources/live-auth.md +465 -0
- package/LLMD/resources/live-components.md +168 -26
- package/LLMD/resources/live-logging.md +220 -0
- package/LLMD/resources/live-upload.md +59 -8
- package/LLMD/resources/rest-auth.md +290 -0
- package/README.md +520 -340
- package/app/client/index.html +2 -2
- package/app/client/public/favicon.svg +46 -0
- package/app/client/src/App.tsx +13 -1
- package/app/client/src/assets/fluxstack-static.svg +46 -0
- package/app/client/src/assets/fluxstack.svg +183 -0
- package/app/client/src/components/AppLayout.tsx +139 -9
- package/app/client/src/components/BackButton.tsx +13 -13
- package/app/client/src/components/DemoPage.tsx +4 -4
- package/app/client/src/live/AuthDemo.tsx +334 -0
- package/app/client/src/live/ChatDemo.tsx +2 -2
- package/app/client/src/live/CounterDemo.tsx +12 -12
- package/app/client/src/live/FormDemo.tsx +2 -2
- package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
- package/app/client/src/live/RoomChatDemo.tsx +24 -16
- package/app/client/src/main.tsx +13 -13
- package/app/client/src/pages/ApiTestPage.tsx +6 -6
- package/app/client/src/pages/HomePage.tsx +80 -52
- package/app/server/auth/AuthManager.ts +213 -0
- package/app/server/auth/DevAuthProvider.ts +66 -0
- package/app/server/auth/HashManager.ts +123 -0
- package/app/server/auth/JWTAuthProvider.example.ts +101 -0
- package/app/server/auth/RateLimiter.ts +106 -0
- package/app/server/auth/contracts.ts +192 -0
- package/app/server/auth/guards/SessionGuard.ts +167 -0
- package/app/server/auth/guards/TokenGuard.ts +202 -0
- package/app/server/auth/index.ts +174 -0
- package/app/server/auth/middleware.ts +163 -0
- package/app/server/auth/providers/InMemoryProvider.ts +162 -0
- package/app/server/auth/sessions/SessionManager.ts +164 -0
- package/app/server/cache/CacheManager.ts +81 -0
- package/app/server/cache/MemoryDriver.ts +112 -0
- package/app/server/cache/contracts.ts +49 -0
- package/app/server/cache/index.ts +42 -0
- package/app/server/index.ts +14 -0
- package/app/server/live/LiveAdminPanel.ts +174 -0
- package/app/server/live/LiveChat.ts +78 -77
- package/app/server/live/LiveCounter.ts +1 -0
- package/app/server/live/LiveForm.ts +1 -0
- package/app/server/live/LiveLocalCounter.ts +38 -32
- package/app/server/live/LiveProtectedChat.ts +151 -0
- package/app/server/live/LiveRoomChat.ts +1 -0
- package/app/server/live/LiveUpload.ts +1 -0
- package/app/server/live/register-components.ts +19 -19
- package/app/server/routes/auth.routes.ts +278 -0
- package/app/server/routes/index.ts +2 -0
- package/config/index.ts +8 -0
- package/config/system/auth.config.ts +49 -0
- package/config/system/runtime.config.ts +4 -0
- package/config/system/session.config.ts +33 -0
- package/core/build/optimizer.ts +235 -235
- package/core/client/LiveComponentsProvider.tsx +76 -5
- package/core/client/components/Live.tsx +17 -10
- package/core/client/components/LiveDebugger.tsx +1324 -0
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
- package/core/client/hooks/useLiveComponent.ts +58 -5
- package/core/client/hooks/useLiveDebugger.ts +392 -0
- package/core/client/index.ts +16 -1
- package/core/framework/server.ts +36 -4
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +19 -8
- package/core/plugins/built-in/monitoring/index.ts +10 -3
- package/core/plugins/built-in/vite/index.ts +151 -20
- package/core/plugins/config.ts +5 -4
- package/core/plugins/discovery.ts +11 -2
- package/core/plugins/manager.ts +11 -5
- package/core/plugins/module-resolver.ts +1 -1
- package/core/plugins/registry.ts +53 -25
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +134 -50
- package/core/server/live/FileUploadManager.ts +188 -24
- package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
- package/core/server/live/LiveDebugger.ts +462 -0
- package/core/server/live/LiveLogger.ts +144 -0
- package/core/server/live/LiveRoomManager.ts +22 -5
- package/core/server/live/StateSignature.ts +704 -643
- package/core/server/live/WebSocketConnectionManager.ts +11 -10
- package/core/server/live/auth/LiveAuthContext.ts +71 -0
- package/core/server/live/auth/LiveAuthManager.ts +304 -0
- package/core/server/live/auth/index.ts +19 -0
- package/core/server/live/auth/types.ts +179 -0
- package/core/server/live/auto-generated-components.ts +8 -2
- package/core/server/live/index.ts +16 -0
- package/core/server/live/websocket-plugin.ts +323 -22
- package/core/server/plugins/static-files-plugin.ts +179 -69
- package/core/templates/create-project.ts +0 -3
- package/core/types/build.ts +219 -219
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +278 -22
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/index.ts +5 -2
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/version.ts +6 -6
- package/package.json +1 -8
- package/plugins/crypto-auth/index.ts +6 -0
- package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
- package/plugins/crypto-auth/server/index.ts +24 -21
- package/rest-tests/README.md +57 -0
- package/rest-tests/auth-token.http +113 -0
- package/rest-tests/auth.http +112 -0
- package/rest-tests/rooms-token.http +69 -0
- package/rest-tests/users-token.http +62 -0
- package/.dockerignore +0 -81
- package/Dockerfile +0 -70
- package/LIVE_COMPONENTS_REVIEW.md +0 -781
- package/app/client/src/assets/react.svg +0 -1
|
@@ -1,215 +1,215 @@
|
|
|
1
|
-
// 🚀 Adaptive Chunk Sizing - Dynamic chunk size adjustment based on connection speed
|
|
2
|
-
// Automatically optimizes upload speed by adjusting chunk sizes
|
|
3
|
-
|
|
4
|
-
export interface AdaptiveChunkConfig {
|
|
5
|
-
minChunkSize: number // Minimum chunk size (default: 16KB)
|
|
6
|
-
maxChunkSize: number // Maximum chunk size (default: 1MB)
|
|
7
|
-
initialChunkSize: number // Starting chunk size (default: 64KB)
|
|
8
|
-
targetLatency: number // Target latency per chunk in ms (default: 200ms)
|
|
9
|
-
adjustmentFactor: number // How aggressively to adjust (default: 1.5)
|
|
10
|
-
measurementWindow: number // Number of chunks to measure (default: 3)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ChunkMetrics {
|
|
14
|
-
chunkIndex: number
|
|
15
|
-
chunkSize: number
|
|
16
|
-
startTime: number
|
|
17
|
-
endTime: number
|
|
18
|
-
latency: number
|
|
19
|
-
throughput: number // bytes per second
|
|
20
|
-
success: boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class AdaptiveChunkSizer {
|
|
24
|
-
private config: Required<AdaptiveChunkConfig>
|
|
25
|
-
private currentChunkSize: number
|
|
26
|
-
private metrics: ChunkMetrics[] = []
|
|
27
|
-
private consecutiveErrors = 0
|
|
28
|
-
private consecutiveSuccesses = 0
|
|
29
|
-
|
|
30
|
-
constructor(config: Partial<AdaptiveChunkConfig> = {}) {
|
|
31
|
-
this.config = {
|
|
32
|
-
minChunkSize: config.minChunkSize ?? 16 * 1024, // 16KB
|
|
33
|
-
maxChunkSize: config.maxChunkSize ?? 1024 * 1024, // 1MB
|
|
34
|
-
initialChunkSize: config.initialChunkSize ?? 64 * 1024, // 64KB
|
|
35
|
-
targetLatency: config.targetLatency ?? 200, // 200ms
|
|
36
|
-
adjustmentFactor: config.adjustmentFactor ?? 1.5,
|
|
37
|
-
measurementWindow: config.measurementWindow ?? 3
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
this.currentChunkSize = this.config.initialChunkSize
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get the current optimal chunk size
|
|
45
|
-
*/
|
|
46
|
-
getChunkSize(): number {
|
|
47
|
-
return this.currentChunkSize
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Record the start of a chunk upload
|
|
52
|
-
*/
|
|
53
|
-
recordChunkStart(chunkIndex: number): number {
|
|
54
|
-
return Date.now()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Record the completion of a chunk upload and adjust chunk size
|
|
59
|
-
*/
|
|
60
|
-
recordChunkComplete(
|
|
61
|
-
chunkIndex: number,
|
|
62
|
-
chunkSize: number,
|
|
63
|
-
startTime: number,
|
|
64
|
-
success: boolean
|
|
65
|
-
): void {
|
|
66
|
-
const endTime = Date.now()
|
|
67
|
-
const latency = endTime - startTime
|
|
68
|
-
const throughput = success ? (chunkSize / latency) * 1000 : 0 // bytes per second
|
|
69
|
-
|
|
70
|
-
const metric: ChunkMetrics = {
|
|
71
|
-
chunkIndex,
|
|
72
|
-
chunkSize,
|
|
73
|
-
startTime,
|
|
74
|
-
endTime,
|
|
75
|
-
latency,
|
|
76
|
-
throughput,
|
|
77
|
-
success
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.metrics.push(metric)
|
|
81
|
-
|
|
82
|
-
// Keep only recent measurements
|
|
83
|
-
if (this.metrics.length > this.config.measurementWindow * 2) {
|
|
84
|
-
this.metrics = this.metrics.slice(-this.config.measurementWindow * 2)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (success) {
|
|
88
|
-
this.consecutiveSuccesses++
|
|
89
|
-
this.consecutiveErrors = 0
|
|
90
|
-
this.adjustChunkSizeUp(latency)
|
|
91
|
-
} else {
|
|
92
|
-
this.consecutiveErrors++
|
|
93
|
-
this.consecutiveSuccesses = 0
|
|
94
|
-
this.adjustChunkSizeDown()
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
console.log(`📊 Adaptive Chunk Stats:`, {
|
|
98
|
-
chunkIndex,
|
|
99
|
-
currentSize: this.formatBytes(this.currentChunkSize),
|
|
100
|
-
latency: `${latency}ms`,
|
|
101
|
-
throughput: `${this.formatBytes(throughput)}/s`,
|
|
102
|
-
avgThroughput: `${this.formatBytes(this.getAverageThroughput())}/s`,
|
|
103
|
-
success
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Increase chunk size if connection is fast
|
|
109
|
-
*/
|
|
110
|
-
private adjustChunkSizeUp(latency: number): void {
|
|
111
|
-
// Only increase if we have enough successful measurements
|
|
112
|
-
if (this.consecutiveSuccesses < 2) return
|
|
113
|
-
|
|
114
|
-
// Only increase if latency is below target
|
|
115
|
-
if (latency > this.config.targetLatency) return
|
|
116
|
-
|
|
117
|
-
// Calculate new chunk size based on how much faster we are than target
|
|
118
|
-
const latencyRatio = this.config.targetLatency / latency
|
|
119
|
-
let newSize = Math.floor(this.currentChunkSize * Math.min(latencyRatio, this.config.adjustmentFactor))
|
|
120
|
-
|
|
121
|
-
// Cap at max chunk size
|
|
122
|
-
newSize = Math.min(newSize, this.config.maxChunkSize)
|
|
123
|
-
|
|
124
|
-
if (newSize > this.currentChunkSize) {
|
|
125
|
-
console.log(`⬆️ Increasing chunk size: ${this.formatBytes(this.currentChunkSize)} → ${this.formatBytes(newSize)}`)
|
|
126
|
-
this.currentChunkSize = newSize
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Decrease chunk size if connection is slow or unstable
|
|
132
|
-
*/
|
|
133
|
-
private adjustChunkSizeDown(): void {
|
|
134
|
-
// Decrease more aggressively on errors
|
|
135
|
-
const decreaseFactor = this.consecutiveErrors > 1 ? 2 : this.config.adjustmentFactor
|
|
136
|
-
|
|
137
|
-
let newSize = Math.floor(this.currentChunkSize / decreaseFactor)
|
|
138
|
-
|
|
139
|
-
// Cap at min chunk size
|
|
140
|
-
newSize = Math.max(newSize, this.config.minChunkSize)
|
|
141
|
-
|
|
142
|
-
if (newSize < this.currentChunkSize) {
|
|
143
|
-
console.log(`⬇️ Decreasing chunk size: ${this.formatBytes(this.currentChunkSize)} → ${this.formatBytes(newSize)}`)
|
|
144
|
-
this.currentChunkSize = newSize
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Get average throughput from recent measurements
|
|
150
|
-
*/
|
|
151
|
-
getAverageThroughput(): number {
|
|
152
|
-
if (this.metrics.length === 0) return 0
|
|
153
|
-
|
|
154
|
-
const recentMetrics = this.metrics
|
|
155
|
-
.slice(-this.config.measurementWindow)
|
|
156
|
-
.filter(m => m.success)
|
|
157
|
-
|
|
158
|
-
if (recentMetrics.length === 0) return 0
|
|
159
|
-
|
|
160
|
-
const totalThroughput = recentMetrics.reduce((sum, m) => sum + m.throughput, 0)
|
|
161
|
-
return totalThroughput / recentMetrics.length
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Get average latency from recent measurements
|
|
166
|
-
*/
|
|
167
|
-
getAverageLatency(): number {
|
|
168
|
-
if (this.metrics.length === 0) return 0
|
|
169
|
-
|
|
170
|
-
const recentMetrics = this.metrics
|
|
171
|
-
.slice(-this.config.measurementWindow)
|
|
172
|
-
.filter(m => m.success)
|
|
173
|
-
|
|
174
|
-
if (recentMetrics.length === 0) return 0
|
|
175
|
-
|
|
176
|
-
const totalLatency = recentMetrics.reduce((sum, m) => sum + m.latency, 0)
|
|
177
|
-
return totalLatency / recentMetrics.length
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Get current performance statistics
|
|
182
|
-
*/
|
|
183
|
-
getStats() {
|
|
184
|
-
return {
|
|
185
|
-
currentChunkSize: this.currentChunkSize,
|
|
186
|
-
averageThroughput: this.getAverageThroughput(),
|
|
187
|
-
averageLatency: this.getAverageLatency(),
|
|
188
|
-
consecutiveSuccesses: this.consecutiveSuccesses,
|
|
189
|
-
consecutiveErrors: this.consecutiveErrors,
|
|
190
|
-
totalMeasurements: this.metrics.length,
|
|
191
|
-
config: this.config
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Reset the adaptive chunking state
|
|
197
|
-
*/
|
|
198
|
-
reset(): void {
|
|
199
|
-
this.currentChunkSize = this.config.initialChunkSize
|
|
200
|
-
this.metrics = []
|
|
201
|
-
this.consecutiveErrors = 0
|
|
202
|
-
this.consecutiveSuccesses = 0
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Format bytes for display
|
|
207
|
-
*/
|
|
208
|
-
private formatBytes(bytes: number): string {
|
|
209
|
-
if (bytes === 0) return '0 B'
|
|
210
|
-
const k = 1024
|
|
211
|
-
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
212
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
213
|
-
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
|
|
214
|
-
}
|
|
215
|
-
}
|
|
1
|
+
// 🚀 Adaptive Chunk Sizing - Dynamic chunk size adjustment based on connection speed
|
|
2
|
+
// Automatically optimizes upload speed by adjusting chunk sizes
|
|
3
|
+
|
|
4
|
+
export interface AdaptiveChunkConfig {
|
|
5
|
+
minChunkSize: number // Minimum chunk size (default: 16KB)
|
|
6
|
+
maxChunkSize: number // Maximum chunk size (default: 1MB)
|
|
7
|
+
initialChunkSize: number // Starting chunk size (default: 64KB)
|
|
8
|
+
targetLatency: number // Target latency per chunk in ms (default: 200ms)
|
|
9
|
+
adjustmentFactor: number // How aggressively to adjust (default: 1.5)
|
|
10
|
+
measurementWindow: number // Number of chunks to measure (default: 3)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ChunkMetrics {
|
|
14
|
+
chunkIndex: number
|
|
15
|
+
chunkSize: number
|
|
16
|
+
startTime: number
|
|
17
|
+
endTime: number
|
|
18
|
+
latency: number
|
|
19
|
+
throughput: number // bytes per second
|
|
20
|
+
success: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class AdaptiveChunkSizer {
|
|
24
|
+
private config: Required<AdaptiveChunkConfig>
|
|
25
|
+
private currentChunkSize: number
|
|
26
|
+
private metrics: ChunkMetrics[] = []
|
|
27
|
+
private consecutiveErrors = 0
|
|
28
|
+
private consecutiveSuccesses = 0
|
|
29
|
+
|
|
30
|
+
constructor(config: Partial<AdaptiveChunkConfig> = {}) {
|
|
31
|
+
this.config = {
|
|
32
|
+
minChunkSize: config.minChunkSize ?? 16 * 1024, // 16KB
|
|
33
|
+
maxChunkSize: config.maxChunkSize ?? 1024 * 1024, // 1MB
|
|
34
|
+
initialChunkSize: config.initialChunkSize ?? 64 * 1024, // 64KB
|
|
35
|
+
targetLatency: config.targetLatency ?? 200, // 200ms
|
|
36
|
+
adjustmentFactor: config.adjustmentFactor ?? 1.5,
|
|
37
|
+
measurementWindow: config.measurementWindow ?? 3
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.currentChunkSize = this.config.initialChunkSize
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the current optimal chunk size
|
|
45
|
+
*/
|
|
46
|
+
getChunkSize(): number {
|
|
47
|
+
return this.currentChunkSize
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Record the start of a chunk upload
|
|
52
|
+
*/
|
|
53
|
+
recordChunkStart(chunkIndex: number): number {
|
|
54
|
+
return Date.now()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Record the completion of a chunk upload and adjust chunk size
|
|
59
|
+
*/
|
|
60
|
+
recordChunkComplete(
|
|
61
|
+
chunkIndex: number,
|
|
62
|
+
chunkSize: number,
|
|
63
|
+
startTime: number,
|
|
64
|
+
success: boolean
|
|
65
|
+
): void {
|
|
66
|
+
const endTime = Date.now()
|
|
67
|
+
const latency = endTime - startTime
|
|
68
|
+
const throughput = success ? (chunkSize / latency) * 1000 : 0 // bytes per second
|
|
69
|
+
|
|
70
|
+
const metric: ChunkMetrics = {
|
|
71
|
+
chunkIndex,
|
|
72
|
+
chunkSize,
|
|
73
|
+
startTime,
|
|
74
|
+
endTime,
|
|
75
|
+
latency,
|
|
76
|
+
throughput,
|
|
77
|
+
success
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.metrics.push(metric)
|
|
81
|
+
|
|
82
|
+
// Keep only recent measurements
|
|
83
|
+
if (this.metrics.length > this.config.measurementWindow * 2) {
|
|
84
|
+
this.metrics = this.metrics.slice(-this.config.measurementWindow * 2)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (success) {
|
|
88
|
+
this.consecutiveSuccesses++
|
|
89
|
+
this.consecutiveErrors = 0
|
|
90
|
+
this.adjustChunkSizeUp(latency)
|
|
91
|
+
} else {
|
|
92
|
+
this.consecutiveErrors++
|
|
93
|
+
this.consecutiveSuccesses = 0
|
|
94
|
+
this.adjustChunkSizeDown()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(`📊 Adaptive Chunk Stats:`, {
|
|
98
|
+
chunkIndex,
|
|
99
|
+
currentSize: this.formatBytes(this.currentChunkSize),
|
|
100
|
+
latency: `${latency}ms`,
|
|
101
|
+
throughput: `${this.formatBytes(throughput)}/s`,
|
|
102
|
+
avgThroughput: `${this.formatBytes(this.getAverageThroughput())}/s`,
|
|
103
|
+
success
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Increase chunk size if connection is fast
|
|
109
|
+
*/
|
|
110
|
+
private adjustChunkSizeUp(latency: number): void {
|
|
111
|
+
// Only increase if we have enough successful measurements
|
|
112
|
+
if (this.consecutiveSuccesses < 2) return
|
|
113
|
+
|
|
114
|
+
// Only increase if latency is below target
|
|
115
|
+
if (latency > this.config.targetLatency) return
|
|
116
|
+
|
|
117
|
+
// Calculate new chunk size based on how much faster we are than target
|
|
118
|
+
const latencyRatio = this.config.targetLatency / latency
|
|
119
|
+
let newSize = Math.floor(this.currentChunkSize * Math.min(latencyRatio, this.config.adjustmentFactor))
|
|
120
|
+
|
|
121
|
+
// Cap at max chunk size
|
|
122
|
+
newSize = Math.min(newSize, this.config.maxChunkSize)
|
|
123
|
+
|
|
124
|
+
if (newSize > this.currentChunkSize) {
|
|
125
|
+
console.log(`⬆️ Increasing chunk size: ${this.formatBytes(this.currentChunkSize)} → ${this.formatBytes(newSize)}`)
|
|
126
|
+
this.currentChunkSize = newSize
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Decrease chunk size if connection is slow or unstable
|
|
132
|
+
*/
|
|
133
|
+
private adjustChunkSizeDown(): void {
|
|
134
|
+
// Decrease more aggressively on errors
|
|
135
|
+
const decreaseFactor = this.consecutiveErrors > 1 ? 2 : this.config.adjustmentFactor
|
|
136
|
+
|
|
137
|
+
let newSize = Math.floor(this.currentChunkSize / decreaseFactor)
|
|
138
|
+
|
|
139
|
+
// Cap at min chunk size
|
|
140
|
+
newSize = Math.max(newSize, this.config.minChunkSize)
|
|
141
|
+
|
|
142
|
+
if (newSize < this.currentChunkSize) {
|
|
143
|
+
console.log(`⬇️ Decreasing chunk size: ${this.formatBytes(this.currentChunkSize)} → ${this.formatBytes(newSize)}`)
|
|
144
|
+
this.currentChunkSize = newSize
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get average throughput from recent measurements
|
|
150
|
+
*/
|
|
151
|
+
getAverageThroughput(): number {
|
|
152
|
+
if (this.metrics.length === 0) return 0
|
|
153
|
+
|
|
154
|
+
const recentMetrics = this.metrics
|
|
155
|
+
.slice(-this.config.measurementWindow)
|
|
156
|
+
.filter(m => m.success)
|
|
157
|
+
|
|
158
|
+
if (recentMetrics.length === 0) return 0
|
|
159
|
+
|
|
160
|
+
const totalThroughput = recentMetrics.reduce((sum, m) => sum + m.throughput, 0)
|
|
161
|
+
return totalThroughput / recentMetrics.length
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get average latency from recent measurements
|
|
166
|
+
*/
|
|
167
|
+
getAverageLatency(): number {
|
|
168
|
+
if (this.metrics.length === 0) return 0
|
|
169
|
+
|
|
170
|
+
const recentMetrics = this.metrics
|
|
171
|
+
.slice(-this.config.measurementWindow)
|
|
172
|
+
.filter(m => m.success)
|
|
173
|
+
|
|
174
|
+
if (recentMetrics.length === 0) return 0
|
|
175
|
+
|
|
176
|
+
const totalLatency = recentMetrics.reduce((sum, m) => sum + m.latency, 0)
|
|
177
|
+
return totalLatency / recentMetrics.length
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get current performance statistics
|
|
182
|
+
*/
|
|
183
|
+
getStats() {
|
|
184
|
+
return {
|
|
185
|
+
currentChunkSize: this.currentChunkSize,
|
|
186
|
+
averageThroughput: this.getAverageThroughput(),
|
|
187
|
+
averageLatency: this.getAverageLatency(),
|
|
188
|
+
consecutiveSuccesses: this.consecutiveSuccesses,
|
|
189
|
+
consecutiveErrors: this.consecutiveErrors,
|
|
190
|
+
totalMeasurements: this.metrics.length,
|
|
191
|
+
config: this.config
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Reset the adaptive chunking state
|
|
197
|
+
*/
|
|
198
|
+
reset(): void {
|
|
199
|
+
this.currentChunkSize = this.config.initialChunkSize
|
|
200
|
+
this.metrics = []
|
|
201
|
+
this.consecutiveErrors = 0
|
|
202
|
+
this.consecutiveSuccesses = 0
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Format bytes for display
|
|
207
|
+
*/
|
|
208
|
+
private formatBytes(bytes: number): string {
|
|
209
|
+
if (bytes === 0) return '0 B'
|
|
210
|
+
const k = 1024
|
|
211
|
+
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
212
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
213
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -67,6 +67,8 @@ export interface LiveComponentProxy<
|
|
|
67
67
|
readonly $status: 'synced' | 'disconnected' | 'connecting' | 'reconnecting' | 'loading' | 'mounting' | 'error'
|
|
68
68
|
readonly $componentId: string | null
|
|
69
69
|
readonly $dirty: boolean
|
|
70
|
+
/** Whether the WebSocket connection is authenticated on the server */
|
|
71
|
+
readonly $authenticated: boolean
|
|
70
72
|
|
|
71
73
|
// Methods
|
|
72
74
|
$call: (action: string, payload?: any) => Promise<void>
|
|
@@ -159,12 +161,22 @@ export interface UseLiveComponentOptions extends HybridComponentOptions {
|
|
|
159
161
|
syncMode?: 'immediate' | 'debounced' | 'manual'
|
|
160
162
|
/** Persistir estado em localStorage (rehydration). Default: true */
|
|
161
163
|
persistState?: boolean
|
|
164
|
+
/**
|
|
165
|
+
* Label de debug para identificar esta instância no Live Debugger.
|
|
166
|
+
* Aparece no lugar do componentId no painel de debug.
|
|
167
|
+
* Só tem efeito em development.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* Live.use(LiveCounter, { debugLabel: 'Header Counter' })
|
|
171
|
+
* Live.use(LiveChat, { debugLabel: 'Main Chat' })
|
|
172
|
+
*/
|
|
173
|
+
debugLabel?: string
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
// ===== Propriedades Reservadas =====
|
|
165
177
|
|
|
166
178
|
const RESERVED_PROPS = new Set([
|
|
167
|
-
'$state', '$connected', '$loading', '$error', '$status', '$componentId', '$dirty',
|
|
179
|
+
'$state', '$connected', '$loading', '$error', '$status', '$componentId', '$dirty', '$authenticated',
|
|
168
180
|
'$call', '$callAndWait', '$mount', '$unmount', '$refresh', '$set', '$onBroadcast', '$updateLocal',
|
|
169
181
|
'$room', '$rooms', '$field', '$sync',
|
|
170
182
|
'then', 'toJSON', 'valueOf', 'toString',
|
|
@@ -262,6 +274,7 @@ export function useLiveComponent<
|
|
|
262
274
|
// WebSocket context
|
|
263
275
|
const {
|
|
264
276
|
connected,
|
|
277
|
+
authenticated: wsAuthenticated,
|
|
265
278
|
sendMessage,
|
|
266
279
|
sendMessageAndWait,
|
|
267
280
|
registerComponent,
|
|
@@ -286,6 +299,7 @@ export function useLiveComponent<
|
|
|
286
299
|
const broadcastHandlerRef = useRef<((event: { type: string; data: any }) => void) | null>(null)
|
|
287
300
|
const roomMessageHandlers = useRef<Set<(msg: RoomServerMessage) => void>>(new Set())
|
|
288
301
|
const roomManagerRef = useRef<RoomManager | null>(null)
|
|
302
|
+
const mountFnRef = useRef<(() => Promise<void>) | null>(null)
|
|
289
303
|
|
|
290
304
|
// State
|
|
291
305
|
const stateData = store((s) => s.state)
|
|
@@ -295,6 +309,7 @@ export function useLiveComponent<
|
|
|
295
309
|
const [error, setError] = useState<string | null>(null)
|
|
296
310
|
const [rehydrating, setRehydrating] = useState(false)
|
|
297
311
|
const [mountFailed, setMountFailed] = useState(false) // Previne loop infinito de mount
|
|
312
|
+
const [authDenied, setAuthDenied] = useState(false) // Track if mount failed due to AUTH_DENIED
|
|
298
313
|
|
|
299
314
|
const log = useCallback((msg: string, data?: any) => {
|
|
300
315
|
if (debug) console.log(`[${componentName}] ${msg}`, data || '')
|
|
@@ -348,7 +363,7 @@ export function useLiveComponent<
|
|
|
348
363
|
const response = await sendMessageAndWait({
|
|
349
364
|
type: 'COMPONENT_MOUNT',
|
|
350
365
|
componentId: instanceId.current,
|
|
351
|
-
payload: { component: componentName, props: initialState, room, userId }
|
|
366
|
+
payload: { component: componentName, props: initialState, room, userId, debugLabel: options.debugLabel }
|
|
352
367
|
}, 5000)
|
|
353
368
|
|
|
354
369
|
if (response?.success && response?.result?.componentId) {
|
|
@@ -371,7 +386,11 @@ export function useLiveComponent<
|
|
|
371
386
|
}
|
|
372
387
|
} catch (err: any) {
|
|
373
388
|
setError(err.message)
|
|
374
|
-
|
|
389
|
+
// Track if auth was the reason for failure
|
|
390
|
+
if (err.message?.includes('AUTH_DENIED')) {
|
|
391
|
+
setAuthDenied(true)
|
|
392
|
+
}
|
|
393
|
+
setMountFailed(true) // Previne loop infinito para TODOS os erros
|
|
375
394
|
onError?.(err.message)
|
|
376
395
|
if (!fallbackToLocal) throw err
|
|
377
396
|
} finally {
|
|
@@ -380,6 +399,9 @@ export function useLiveComponent<
|
|
|
380
399
|
}
|
|
381
400
|
}, [connected, componentName, initialState, room, userId, sendMessageAndWait, updateState, log, onMount, onError, fallbackToLocal, mountFailed])
|
|
382
401
|
|
|
402
|
+
// Keep mount function ref updated
|
|
403
|
+
mountFnRef.current = mount
|
|
404
|
+
|
|
383
405
|
// ===== Unmount =====
|
|
384
406
|
const unmount = useCallback(async () => {
|
|
385
407
|
if (!componentId || !connected) return
|
|
@@ -573,6 +595,14 @@ export function useLiveComponent<
|
|
|
573
595
|
}
|
|
574
596
|
}
|
|
575
597
|
break
|
|
598
|
+
case 'STATE_DELTA':
|
|
599
|
+
if (message.payload?.delta) {
|
|
600
|
+
const oldState = storeRef.current?.getState().state ?? stateData
|
|
601
|
+
const mergedState = { ...oldState, ...message.payload.delta } as TState
|
|
602
|
+
updateState(mergedState)
|
|
603
|
+
onStateChange?.(mergedState, oldState)
|
|
604
|
+
}
|
|
605
|
+
break
|
|
576
606
|
case 'STATE_REHYDRATED':
|
|
577
607
|
if (message.payload?.state && message.payload?.newComponentId) {
|
|
578
608
|
setComponentId(message.payload.newComponentId)
|
|
@@ -620,6 +650,28 @@ export function useLiveComponent<
|
|
|
620
650
|
}
|
|
621
651
|
}, [connected, autoMount, mount, componentId, rehydrating, rehydrate, mountFailed])
|
|
622
652
|
|
|
653
|
+
// ===== Auto Re-mount on Auth Change =====
|
|
654
|
+
// When auth changes from false to true and component failed due to AUTH_DENIED, retry mount
|
|
655
|
+
const prevAuthRef = useRef(wsAuthenticated)
|
|
656
|
+
useEffect(() => {
|
|
657
|
+
const wasNotAuthenticated = !prevAuthRef.current
|
|
658
|
+
const isNowAuthenticated = wsAuthenticated
|
|
659
|
+
prevAuthRef.current = wsAuthenticated
|
|
660
|
+
|
|
661
|
+
// Only retry if: auth changed from false→true AND we had an auth denial
|
|
662
|
+
if (wasNotAuthenticated && isNowAuthenticated && authDenied) {
|
|
663
|
+
log('Auth changed to authenticated, retrying mount...')
|
|
664
|
+
// Reset flags to allow retry
|
|
665
|
+
setAuthDenied(false)
|
|
666
|
+
setMountFailed(false)
|
|
667
|
+
setError(null)
|
|
668
|
+
mountedRef.current = false
|
|
669
|
+
mountingRef.current = false
|
|
670
|
+
// Small delay to ensure state is updated, use ref to avoid stale closure
|
|
671
|
+
setTimeout(() => mountFnRef.current?.(), 50)
|
|
672
|
+
}
|
|
673
|
+
}, [wsAuthenticated, authDenied, log])
|
|
674
|
+
|
|
623
675
|
// ===== Connection Changes =====
|
|
624
676
|
const prevConnected = useRef(connected)
|
|
625
677
|
useEffect(() => {
|
|
@@ -708,6 +760,7 @@ export function useLiveComponent<
|
|
|
708
760
|
case '$status': return getStatus()
|
|
709
761
|
case '$componentId': return componentId
|
|
710
762
|
case '$dirty': return pendingChanges.current.size > 0
|
|
763
|
+
case '$authenticated': return wsAuthenticated
|
|
711
764
|
case '$call': return call
|
|
712
765
|
case '$callAndWait': return callAndWait
|
|
713
766
|
case '$mount': return mount
|
|
@@ -773,10 +826,10 @@ export function useLiveComponent<
|
|
|
773
826
|
},
|
|
774
827
|
|
|
775
828
|
ownKeys() {
|
|
776
|
-
return [...Object.keys(stateData), '$state', '$connected', '$loading', '$error', '$status', '$componentId', '$dirty', '$call', '$callAndWait', '$mount', '$unmount', '$refresh', '$set', '$field', '$sync', '$onBroadcast', '$updateLocal', '$room', '$rooms']
|
|
829
|
+
return [...Object.keys(stateData), '$state', '$connected', '$loading', '$error', '$status', '$componentId', '$dirty', '$authenticated', '$call', '$callAndWait', '$mount', '$unmount', '$refresh', '$set', '$field', '$sync', '$onBroadcast', '$updateLocal', '$room', '$rooms']
|
|
777
830
|
}
|
|
778
831
|
})
|
|
779
|
-
}, [stateData, connected, loading, error, componentId, call, callAndWait, mount, unmount, refresh, setProperty, optimistic, sendMessageAndWait, createFieldBinding, sync, localVersion, roomManager])
|
|
832
|
+
}, [stateData, connected, wsAuthenticated, loading, error, componentId, call, callAndWait, mount, unmount, refresh, setProperty, optimistic, sendMessageAndWait, createFieldBinding, sync, localVersion, roomManager])
|
|
780
833
|
|
|
781
834
|
return proxy
|
|
782
835
|
}
|