create-fluxstack 1.9.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/LIVE_COMPONENTS_REVIEW.md +781 -0
- 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 +109 -156
- 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 -65
- 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 +13 -10
- package/app/client/src/pages/ApiTestPage.tsx +108 -0
- package/app/client/src/pages/HomePage.tsx +76 -0
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/server/app.ts +1 -4
- package/app/server/controllers/users.controller.ts +36 -44
- package/app/server/index.ts +24 -107
- 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/live/register-components.ts +19 -19
- 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/bundler.ts +4 -1
- package/core/build/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +159 -27
- package/core/build/live-components-generator.ts +70 -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 -558
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/LiveComponentsProvider.tsx +63 -17
- 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 -0
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/hooks/useChunkedUpload.ts +170 -69
- 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 +18 -51
- 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 +227 -47
- package/core/framework/types.ts +2 -2
- package/core/index.ts +23 -4
- package/core/live/ComponentRegistry.ts +7 -3
- package/core/live/types.ts +77 -0
- package/core/plugins/built-in/index.ts +134 -131
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1074
- 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 +94 -306
- package/core/plugins/built-in/vite/vite-dev.ts +82 -0
- 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 +309 -21
- package/core/plugins/types.ts +106 -55
- package/core/server/framework.ts +66 -43
- package/core/server/index.ts +15 -16
- package/core/server/live/ComponentRegistry.ts +91 -75
- package/core/server/live/FileUploadManager.ts +41 -31
- 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 +41 -26
- package/core/server/live/index.ts +14 -0
- package/core/server/live/websocket-plugin.ts +233 -72
- 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 -260
- package/core/server/plugins/swagger.ts +33 -33
- 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 -218
- package/core/types/config.ts +56 -26
- package/core/types/index.ts +4 -4
- package/core/types/plugin.ts +107 -99
- package/core/types/types.ts +490 -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 -66
- 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/eslint.config.js +23 -23
- package/package.json +14 -15
- 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/plugins/crypto-auth/server/middlewares.ts +19 -19
- package/tsconfig.api-strict.json +16 -0
- package/tsconfig.json +10 -14
- package/{app/client/tsconfig.node.json → tsconfig.node.json} +1 -1
- 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/workspace.json +5 -5
- 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/tsconfig.app.json +0 -44
- 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/routes/env-test.ts +0 -110
- package/core/client/hooks/index.ts +0 -7
- package/core/client/hooks/useHybridLiveComponent.ts +0 -631
- package/core/client/hooks/useWebSocket.ts +0 -373
- 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,49 +1,49 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authentication Hook
|
|
3
|
-
* Core FluxStack authentication utilities
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { BaseUser, BaseUserStore } from '../state/index'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Create authentication hook for a user store
|
|
10
|
-
*/
|
|
11
|
-
export function createAuthHook(useUserStore: () => BaseUserStore) {
|
|
12
|
-
return function useAuth() {
|
|
13
|
-
const store = useUserStore()
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
// State
|
|
17
|
-
currentUser: store.currentUser,
|
|
18
|
-
isAuthenticated: store.isAuthenticated,
|
|
19
|
-
isLoading: store.isLoading,
|
|
20
|
-
error: store.error,
|
|
21
|
-
|
|
22
|
-
// Computed
|
|
23
|
-
isAdmin: store.currentUser?.role === 'admin',
|
|
24
|
-
|
|
25
|
-
// Actions
|
|
26
|
-
login: store.login,
|
|
27
|
-
register: store.register,
|
|
28
|
-
logout: store.logout,
|
|
29
|
-
updateProfile: store.updateProfile,
|
|
30
|
-
clearError: store.clearError
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Base auth hook interface
|
|
37
|
-
*/
|
|
38
|
-
export interface AuthHook {
|
|
39
|
-
currentUser: BaseUser | null
|
|
40
|
-
isAuthenticated: boolean
|
|
41
|
-
isLoading: boolean
|
|
42
|
-
error: string | null
|
|
43
|
-
isAdmin: boolean
|
|
44
|
-
login: (credentials: { email: string; password: string }) => Promise<void>
|
|
45
|
-
register: (data: { email: string; password: string; name: string }) => Promise<void>
|
|
46
|
-
logout: () => void
|
|
47
|
-
updateProfile: (data: Partial<BaseUser>) => Promise<void>
|
|
48
|
-
clearError: () => void
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Hook
|
|
3
|
+
* Core FluxStack authentication utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { BaseUser, BaseUserStore } from '../state/index'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create authentication hook for a user store
|
|
10
|
+
*/
|
|
11
|
+
export function createAuthHook(useUserStore: () => BaseUserStore) {
|
|
12
|
+
return function useAuth() {
|
|
13
|
+
const store = useUserStore()
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
// State
|
|
17
|
+
currentUser: store.currentUser,
|
|
18
|
+
isAuthenticated: store.isAuthenticated,
|
|
19
|
+
isLoading: store.isLoading,
|
|
20
|
+
error: store.error,
|
|
21
|
+
|
|
22
|
+
// Computed
|
|
23
|
+
isAdmin: store.currentUser?.role === 'admin',
|
|
24
|
+
|
|
25
|
+
// Actions
|
|
26
|
+
login: store.login,
|
|
27
|
+
register: store.register,
|
|
28
|
+
logout: store.logout,
|
|
29
|
+
updateProfile: store.updateProfile,
|
|
30
|
+
clearError: store.clearError
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Base auth hook interface
|
|
37
|
+
*/
|
|
38
|
+
export interface AuthHook {
|
|
39
|
+
currentUser: BaseUser | null
|
|
40
|
+
isAuthenticated: boolean
|
|
41
|
+
isLoading: boolean
|
|
42
|
+
error: string | null
|
|
43
|
+
isAdmin: boolean
|
|
44
|
+
login: (credentials: { email: string; password: string }) => Promise<void>
|
|
45
|
+
register: (data: { email: string; password: string; name: string }) => Promise<void>
|
|
46
|
+
logout: () => void
|
|
47
|
+
updateProfile: (data: Partial<BaseUser>) => Promise<void>
|
|
48
|
+
clearError: () => void
|
|
49
49
|
}
|
|
@@ -1,20 +1,54 @@
|
|
|
1
1
|
import { useState, useCallback, useRef } from 'react'
|
|
2
|
-
import type
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { AdaptiveChunkSizer, type AdaptiveChunkConfig } from './AdaptiveChunkSizer'
|
|
3
|
+
import type {
|
|
4
|
+
FileUploadStartMessage,
|
|
5
|
+
FileUploadChunkMessage,
|
|
5
6
|
FileUploadCompleteMessage,
|
|
6
7
|
FileUploadProgressResponse,
|
|
7
|
-
FileUploadCompleteResponse
|
|
8
|
-
|
|
8
|
+
FileUploadCompleteResponse,
|
|
9
|
+
BinaryChunkHeader
|
|
10
|
+
} from '@core/types/types'
|
|
9
11
|
|
|
10
12
|
export interface ChunkedUploadOptions {
|
|
11
|
-
chunkSize?: number // Default 64KB
|
|
13
|
+
chunkSize?: number // Default 64KB (used as initial if adaptive is enabled)
|
|
12
14
|
maxFileSize?: number // Default 50MB
|
|
13
15
|
allowedTypes?: string[]
|
|
14
|
-
sendMessageAndWait?: (message: any, timeout?: number) => Promise<any> // WebSocket send function
|
|
16
|
+
sendMessageAndWait?: (message: any, timeout?: number) => Promise<any> // WebSocket send function for JSON
|
|
17
|
+
sendBinaryAndWait?: (data: ArrayBuffer, requestId: string, timeout?: number) => Promise<any> // WebSocket send function for binary
|
|
15
18
|
onProgress?: (progress: number, bytesUploaded: number, totalBytes: number) => void
|
|
16
19
|
onComplete?: (response: FileUploadCompleteResponse) => void
|
|
17
20
|
onError?: (error: string) => void
|
|
21
|
+
// Adaptive chunking options
|
|
22
|
+
adaptiveChunking?: boolean // Enable adaptive chunk sizing (default: false)
|
|
23
|
+
adaptiveConfig?: Partial<AdaptiveChunkConfig> // Adaptive chunking configuration
|
|
24
|
+
// Binary protocol (more efficient, ~33% less data)
|
|
25
|
+
useBinaryProtocol?: boolean // Enable binary chunk protocol (default: true)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates a binary message with header + data
|
|
30
|
+
* Format: [4 bytes header length][JSON header][binary data]
|
|
31
|
+
*/
|
|
32
|
+
function createBinaryChunkMessage(header: BinaryChunkHeader, chunkData: Uint8Array): ArrayBuffer {
|
|
33
|
+
const headerJson = JSON.stringify(header)
|
|
34
|
+
const headerBytes = new TextEncoder().encode(headerJson)
|
|
35
|
+
|
|
36
|
+
// Total size: 4 bytes (header length) + header + data
|
|
37
|
+
const totalSize = 4 + headerBytes.length + chunkData.length
|
|
38
|
+
const buffer = new ArrayBuffer(totalSize)
|
|
39
|
+
const view = new DataView(buffer)
|
|
40
|
+
const uint8View = new Uint8Array(buffer)
|
|
41
|
+
|
|
42
|
+
// Write header length (little-endian)
|
|
43
|
+
view.setUint32(0, headerBytes.length, true)
|
|
44
|
+
|
|
45
|
+
// Write header
|
|
46
|
+
uint8View.set(headerBytes, 4)
|
|
47
|
+
|
|
48
|
+
// Write chunk data
|
|
49
|
+
uint8View.set(chunkData, 4 + headerBytes.length)
|
|
50
|
+
|
|
51
|
+
return buffer
|
|
18
52
|
}
|
|
19
53
|
|
|
20
54
|
export interface ChunkedUploadState {
|
|
@@ -27,7 +61,7 @@ export interface ChunkedUploadState {
|
|
|
27
61
|
}
|
|
28
62
|
|
|
29
63
|
export function useChunkedUpload(componentId: string, options: ChunkedUploadOptions = {}) {
|
|
30
|
-
|
|
64
|
+
|
|
31
65
|
const [state, setState] = useState<ChunkedUploadState>({
|
|
32
66
|
uploading: false,
|
|
33
67
|
progress: 0,
|
|
@@ -42,44 +76,30 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
|
|
|
42
76
|
maxFileSize = 50 * 1024 * 1024, // 50MB default
|
|
43
77
|
allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'],
|
|
44
78
|
sendMessageAndWait,
|
|
79
|
+
sendBinaryAndWait,
|
|
45
80
|
onProgress,
|
|
46
81
|
onComplete,
|
|
47
|
-
onError
|
|
82
|
+
onError,
|
|
83
|
+
adaptiveChunking = false,
|
|
84
|
+
adaptiveConfig,
|
|
85
|
+
useBinaryProtocol = true // Default to binary for efficiency
|
|
48
86
|
} = options
|
|
49
87
|
|
|
88
|
+
// Determine if we can use binary protocol
|
|
89
|
+
const canUseBinary = useBinaryProtocol && sendBinaryAndWait
|
|
90
|
+
|
|
50
91
|
const abortControllerRef = useRef<AbortController | null>(null)
|
|
92
|
+
const adaptiveSizerRef = useRef<AdaptiveChunkSizer | null>(null)
|
|
51
93
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const uint8Array = new Uint8Array(arrayBuffer)
|
|
60
|
-
|
|
61
|
-
// Split binary data into chunks first, then convert each chunk to base64
|
|
62
|
-
const chunks: string[] = []
|
|
63
|
-
for (let i = 0; i < uint8Array.length; i += chunkSize) {
|
|
64
|
-
const chunkEnd = Math.min(i + chunkSize, uint8Array.length)
|
|
65
|
-
const chunkBytes = uint8Array.slice(i, chunkEnd)
|
|
66
|
-
|
|
67
|
-
// Convert chunk to base64
|
|
68
|
-
let binary = ''
|
|
69
|
-
for (let j = 0; j < chunkBytes.length; j++) {
|
|
70
|
-
binary += String.fromCharCode(chunkBytes[j])
|
|
71
|
-
}
|
|
72
|
-
const base64Chunk = btoa(binary)
|
|
73
|
-
chunks.push(base64Chunk)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
resolve(chunks)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
reader.onerror = () => reject(new Error('Failed to read file'))
|
|
80
|
-
reader.readAsArrayBuffer(file)
|
|
94
|
+
// Initialize adaptive chunk sizer if enabled
|
|
95
|
+
if (adaptiveChunking && !adaptiveSizerRef.current) {
|
|
96
|
+
adaptiveSizerRef.current = new AdaptiveChunkSizer({
|
|
97
|
+
initialChunkSize: chunkSize,
|
|
98
|
+
minChunkSize: chunkSize, // Do not go below initial chunk size by default
|
|
99
|
+
maxChunkSize: 1024 * 1024, // 1MB max
|
|
100
|
+
...adaptiveConfig
|
|
81
101
|
})
|
|
82
|
-
}
|
|
102
|
+
}
|
|
83
103
|
|
|
84
104
|
// Start chunked upload
|
|
85
105
|
const uploadFile = useCallback(async (file: File) => {
|
|
@@ -90,8 +110,8 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
|
|
|
90
110
|
return
|
|
91
111
|
}
|
|
92
112
|
|
|
93
|
-
// Validate file
|
|
94
|
-
if (!allowedTypes.includes(file.type)) {
|
|
113
|
+
// Validate file type (skip if allowedTypes is empty = accept all)
|
|
114
|
+
if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
|
|
95
115
|
const error = `Invalid file type: ${file.type}. Allowed: ${allowedTypes.join(', ')}`
|
|
96
116
|
setState(prev => ({ ...prev, error }))
|
|
97
117
|
onError?.(error)
|
|
@@ -120,13 +140,23 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
|
|
|
120
140
|
totalBytes: file.size
|
|
121
141
|
})
|
|
122
142
|
|
|
123
|
-
console.log('🚀 Starting chunked upload:', {
|
|
143
|
+
console.log('🚀 Starting chunked upload:', {
|
|
144
|
+
uploadId,
|
|
145
|
+
filename: file.name,
|
|
146
|
+
size: file.size,
|
|
147
|
+
adaptiveChunking,
|
|
148
|
+
protocol: canUseBinary ? 'binary' : 'base64'
|
|
149
|
+
})
|
|
124
150
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
151
|
+
// Reset adaptive sizer for new upload
|
|
152
|
+
if (adaptiveSizerRef.current) {
|
|
153
|
+
adaptiveSizerRef.current.reset()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Get initial chunk size (adaptive or fixed)
|
|
157
|
+
const initialChunkSize = adaptiveSizerRef.current?.getChunkSize() ?? chunkSize
|
|
128
158
|
|
|
129
|
-
console.log(`📦
|
|
159
|
+
console.log(`📦 Initial chunk size: ${initialChunkSize} bytes${adaptiveChunking ? ' (adaptive)' : ''}`)
|
|
130
160
|
|
|
131
161
|
// Start upload
|
|
132
162
|
const startMessage: FileUploadStartMessage = {
|
|
@@ -147,35 +177,106 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
|
|
|
147
177
|
|
|
148
178
|
console.log('✅ Upload started successfully')
|
|
149
179
|
|
|
150
|
-
|
|
151
|
-
|
|
180
|
+
let offset = 0
|
|
181
|
+
let chunkIndex = 0
|
|
182
|
+
const estimatedTotalChunks = Math.ceil(file.size / initialChunkSize)
|
|
183
|
+
|
|
184
|
+
// Send chunks dynamically with adaptive sizing (read slice per chunk)
|
|
185
|
+
while (offset < file.size) {
|
|
152
186
|
if (abortControllerRef.current?.signal.aborted) {
|
|
153
187
|
throw new Error('Upload cancelled')
|
|
154
188
|
}
|
|
155
189
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
190
|
+
// Get current chunk size (adaptive or fixed)
|
|
191
|
+
const currentChunkSize = adaptiveSizerRef.current?.getChunkSize() ?? chunkSize
|
|
192
|
+
const chunkEnd = Math.min(offset + currentChunkSize, file.size)
|
|
193
|
+
const sliceBuffer = await file.slice(offset, chunkEnd).arrayBuffer()
|
|
194
|
+
const chunkBytes = new Uint8Array(sliceBuffer)
|
|
195
|
+
|
|
196
|
+
// Record chunk start time for adaptive sizing
|
|
197
|
+
const chunkStartTime = adaptiveSizerRef.current?.recordChunkStart(chunkIndex) ?? 0
|
|
198
|
+
const requestId = `chunk-${uploadId}-${chunkIndex}`
|
|
199
|
+
|
|
200
|
+
console.log(`📤 Sending chunk ${chunkIndex + 1} (size: ${chunkBytes.length} bytes)${canUseBinary ? ' [binary]' : ' [base64]'}`)
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
let progressResponse: FileUploadProgressResponse | undefined
|
|
204
|
+
|
|
205
|
+
if (canUseBinary) {
|
|
206
|
+
// Binary protocol: Send header + raw bytes (more efficient)
|
|
207
|
+
const header: BinaryChunkHeader = {
|
|
208
|
+
type: 'FILE_UPLOAD_CHUNK',
|
|
209
|
+
componentId,
|
|
210
|
+
uploadId,
|
|
211
|
+
chunkIndex,
|
|
212
|
+
totalChunks: estimatedTotalChunks,
|
|
213
|
+
requestId
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const binaryMessage = createBinaryChunkMessage(header, chunkBytes)
|
|
217
|
+
progressResponse = await sendBinaryAndWait!(binaryMessage, requestId, 10000) as FileUploadProgressResponse
|
|
218
|
+
} else {
|
|
219
|
+
// JSON protocol: Convert to base64 (legacy/fallback)
|
|
220
|
+
let binary = ''
|
|
221
|
+
for (let j = 0; j < chunkBytes.length; j++) {
|
|
222
|
+
binary += String.fromCharCode(chunkBytes[j])
|
|
223
|
+
}
|
|
224
|
+
const base64Chunk = btoa(binary)
|
|
225
|
+
|
|
226
|
+
const chunkMessage: FileUploadChunkMessage = {
|
|
227
|
+
type: 'FILE_UPLOAD_CHUNK',
|
|
228
|
+
componentId,
|
|
229
|
+
uploadId,
|
|
230
|
+
chunkIndex,
|
|
231
|
+
totalChunks: estimatedTotalChunks,
|
|
232
|
+
data: base64Chunk,
|
|
233
|
+
requestId
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
progressResponse = await sendMessageAndWait!(chunkMessage, 10000) as FileUploadProgressResponse
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (progressResponse) {
|
|
240
|
+
const { progress, bytesUploaded } = progressResponse
|
|
241
|
+
setState(prev => ({ ...prev, progress, bytesUploaded }))
|
|
242
|
+
onProgress?.(progress, bytesUploaded, file.size)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Record successful chunk upload for adaptive sizing
|
|
246
|
+
if (adaptiveSizerRef.current) {
|
|
247
|
+
adaptiveSizerRef.current.recordChunkComplete(
|
|
248
|
+
chunkIndex,
|
|
249
|
+
chunkBytes.length,
|
|
250
|
+
chunkStartTime,
|
|
251
|
+
true
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
// Record failed chunk for adaptive sizing
|
|
256
|
+
if (adaptiveSizerRef.current) {
|
|
257
|
+
adaptiveSizerRef.current.recordChunkComplete(
|
|
258
|
+
chunkIndex,
|
|
259
|
+
chunkBytes.length,
|
|
260
|
+
chunkStartTime,
|
|
261
|
+
false
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
throw error
|
|
164
265
|
}
|
|
165
266
|
|
|
166
|
-
|
|
267
|
+
offset += chunkBytes.length
|
|
268
|
+
chunkIndex++
|
|
167
269
|
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (progressResponse) {
|
|
172
|
-
const { progress, bytesUploaded } = progressResponse
|
|
173
|
-
setState(prev => ({ ...prev, progress, bytesUploaded }))
|
|
174
|
-
onProgress?.(progress, bytesUploaded, file.size)
|
|
270
|
+
// Small delay to prevent overwhelming the server (only for fixed chunking)
|
|
271
|
+
if (!adaptiveChunking) {
|
|
272
|
+
await new Promise(resolve => setTimeout(resolve, 10))
|
|
175
273
|
}
|
|
274
|
+
}
|
|
176
275
|
|
|
177
|
-
|
|
178
|
-
|
|
276
|
+
// Log final adaptive stats
|
|
277
|
+
if (adaptiveSizerRef.current) {
|
|
278
|
+
const stats = adaptiveSizerRef.current.getStats()
|
|
279
|
+
console.log('📊 Final Adaptive Chunking Stats:', stats)
|
|
179
280
|
}
|
|
180
281
|
|
|
181
282
|
// Complete upload
|
|
@@ -219,10 +320,10 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
|
|
|
219
320
|
maxFileSize,
|
|
220
321
|
chunkSize,
|
|
221
322
|
sendMessageAndWait,
|
|
222
|
-
fileToChunks,
|
|
223
323
|
onProgress,
|
|
224
324
|
onComplete,
|
|
225
|
-
onError
|
|
325
|
+
onError,
|
|
326
|
+
adaptiveChunking
|
|
226
327
|
])
|
|
227
328
|
|
|
228
329
|
// Cancel upload
|
|
@@ -255,4 +356,4 @@ export function useChunkedUpload(componentId: string, options: ChunkedUploadOpti
|
|
|
255
356
|
cancelUpload,
|
|
256
357
|
reset
|
|
257
358
|
}
|
|
258
|
-
}
|
|
359
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { useLiveComponents } from '../LiveComponentsProvider'
|
|
3
|
+
import { useChunkedUpload } from './useChunkedUpload'
|
|
4
|
+
import type { ChunkedUploadOptions } from './useChunkedUpload'
|
|
5
|
+
import type { FileUploadCompleteResponse } from '@core/types/types'
|
|
6
|
+
|
|
7
|
+
type LiveUploadActions = {
|
|
8
|
+
$componentId: string | null
|
|
9
|
+
startUpload: (payload: { fileName: string; fileSize: number; fileType: string }) => Promise<any>
|
|
10
|
+
updateProgress: (payload: { progress: number; bytesUploaded: number; totalBytes: number }) => Promise<any>
|
|
11
|
+
completeUpload: (payload: { fileUrl: string }) => Promise<any>
|
|
12
|
+
failUpload: (payload: { error: string }) => Promise<any>
|
|
13
|
+
reset: () => Promise<any>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LiveChunkedUploadOptions extends Omit<ChunkedUploadOptions, 'sendMessageAndWait' | 'onProgress' | 'onComplete' | 'onError'> {
|
|
17
|
+
onProgress?: (progress: number, bytesUploaded: number, totalBytes: number) => void
|
|
18
|
+
onComplete?: (response: FileUploadCompleteResponse) => void
|
|
19
|
+
onError?: (error: string) => void
|
|
20
|
+
fileUrlResolver?: (fileUrl: string) => string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useLiveChunkedUpload(live: LiveUploadActions, options: LiveChunkedUploadOptions = {}) {
|
|
24
|
+
const { sendMessageAndWait, sendBinaryAndWait } = useLiveComponents()
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
onProgress,
|
|
28
|
+
onComplete,
|
|
29
|
+
onError,
|
|
30
|
+
fileUrlResolver,
|
|
31
|
+
...chunkedOptions
|
|
32
|
+
} = options
|
|
33
|
+
|
|
34
|
+
const componentId = live.$componentId ?? ''
|
|
35
|
+
|
|
36
|
+
const base = useChunkedUpload(componentId, {
|
|
37
|
+
...chunkedOptions,
|
|
38
|
+
sendMessageAndWait,
|
|
39
|
+
sendBinaryAndWait, // Enable binary protocol for efficient uploads
|
|
40
|
+
onProgress: (pct, uploaded, total) => {
|
|
41
|
+
void live.updateProgress({ progress: pct, bytesUploaded: uploaded, totalBytes: total }).catch(() => {})
|
|
42
|
+
onProgress?.(pct, uploaded, total)
|
|
43
|
+
},
|
|
44
|
+
onComplete: (response) => {
|
|
45
|
+
const rawUrl = response.fileUrl || ''
|
|
46
|
+
const resolvedUrl = fileUrlResolver ? fileUrlResolver(rawUrl) : rawUrl
|
|
47
|
+
void live.completeUpload({ fileUrl: resolvedUrl }).catch(() => {})
|
|
48
|
+
onComplete?.(response)
|
|
49
|
+
},
|
|
50
|
+
onError: (error) => {
|
|
51
|
+
void live.failUpload({ error }).catch(() => {})
|
|
52
|
+
onError?.(error)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const uploadFile = useMemo(() => {
|
|
57
|
+
return async (file: File) => {
|
|
58
|
+
if (!live.$componentId) {
|
|
59
|
+
const msg = 'WebSocket not ready. Wait a moment and try again.'
|
|
60
|
+
void live.failUpload({ error: msg }).catch(() => {})
|
|
61
|
+
onError?.(msg)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await live.startUpload({
|
|
66
|
+
fileName: file.name,
|
|
67
|
+
fileSize: file.size,
|
|
68
|
+
fileType: file.type || 'application/octet-stream'
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
await base.uploadFile(file)
|
|
72
|
+
}
|
|
73
|
+
}, [base, live, onError])
|
|
74
|
+
|
|
75
|
+
const reset = useMemo(() => {
|
|
76
|
+
return async () => {
|
|
77
|
+
await live.reset()
|
|
78
|
+
base.reset()
|
|
79
|
+
}
|
|
80
|
+
}, [base, live])
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
...base,
|
|
84
|
+
uploadFile,
|
|
85
|
+
reset
|
|
86
|
+
}
|
|
87
|
+
}
|