create-fluxstack 1.20.0 → 1.21.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.
@@ -3,44 +3,44 @@
3
3
  * Core FluxStack state management utilities
4
4
  */
5
5
 
6
- import { create } from 'zustand'
7
- import { persist, createJSONStorage } from 'zustand/middleware'
8
-
9
- export interface StoreOptions<T> {
10
- name?: string
11
- persist?: boolean
12
- storage?: 'localStorage' | 'sessionStorage'
13
- version?: number
14
- migrate?: (persistedState: unknown, version: number) => T
15
- }
16
-
17
- /**
18
- * Create a Zustand store with FluxStack conventions
19
- */
20
- export function createFluxStore<T>(
21
- storeFactory: (set: (partial: Partial<T> | ((state: T) => Partial<T>)) => void, get: () => T) => T,
22
- options: StoreOptions<T> = {}
23
- ) {
24
- const { name, persist: shouldPersist = false, storage = 'localStorage', version = 1, migrate } = options
25
-
26
- if (shouldPersist && name) {
27
- return create<T>()(
28
- persist(
29
- storeFactory,
30
- {
31
- name,
32
- storage: createJSONStorage(() =>
33
- storage === 'localStorage' ? localStorage : sessionStorage
34
- ),
35
- version,
36
- migrate: migrate as ((persistedState: unknown, version: number) => T) | undefined,
37
- onRehydrateStorage: () => (state) => {
38
- console.log('FluxStack: Store rehydrated', name, state)
39
- }
40
- }
41
- )
42
- )
43
- }
6
+ import { create } from 'zustand'
7
+ import { persist, createJSONStorage } from 'zustand/middleware'
8
+
9
+ export interface StoreOptions<T> {
10
+ name?: string
11
+ persist?: boolean
12
+ storage?: 'localStorage' | 'sessionStorage'
13
+ version?: number
14
+ migrate?: (persistedState: unknown, version: number) => T
15
+ }
16
+
17
+ /**
18
+ * Create a Zustand store with FluxStack conventions
19
+ */
20
+ export function createFluxStore<T>(
21
+ storeFactory: (set: (partial: Partial<T> | ((state: T) => Partial<T>)) => void, get: () => T) => T,
22
+ options: StoreOptions<T> = {}
23
+ ) {
24
+ const { name, persist: shouldPersist = false, storage = 'localStorage', version = 1, migrate } = options
25
+
26
+ if (shouldPersist && name) {
27
+ return create<T>()(
28
+ persist(
29
+ storeFactory,
30
+ {
31
+ name,
32
+ storage: createJSONStorage(() =>
33
+ storage === 'localStorage' ? localStorage : sessionStorage
34
+ ),
35
+ version,
36
+ migrate: migrate as ((persistedState: unknown, version: number) => T) | undefined,
37
+ onRehydrateStorage: () => (state) => {
38
+ console.log('FluxStack: Store rehydrated', name, state)
39
+ }
40
+ }
41
+ )
42
+ )
43
+ }
44
44
 
45
45
  return create<T>()(storeFactory)
46
46
  }
@@ -65,27 +65,27 @@ export interface BaseUserStore {
65
65
  logout: () => void
66
66
  updateProfile: (data: Partial<BaseUser>) => Promise<void>
67
67
  clearError: () => void
68
- setLoading: (loading: boolean) => void
69
- }
70
-
71
- /**
72
- * Create user store with FluxStack conventions
73
- */
74
- export function createUserStore(options: StoreOptions<BaseUserStore> = {}) {
75
- return createFluxStore<BaseUserStore>(
76
- (set, get) => ({
77
- currentUser: null,
78
- isAuthenticated: false,
68
+ setLoading: (loading: boolean) => void
69
+ }
70
+
71
+ /**
72
+ * Create user store with FluxStack conventions
73
+ */
74
+ export function createUserStore(options: StoreOptions<BaseUserStore> = {}) {
75
+ return createFluxStore<BaseUserStore>(
76
+ (set, get) => ({
77
+ currentUser: null,
78
+ isAuthenticated: false,
79
79
  isLoading: false,
80
80
  error: null,
81
81
 
82
- login: async (credentials) => {
83
- set({ isLoading: true, error: null })
84
- try {
85
- const response = await fetch('/api/auth/login', {
86
- method: 'POST',
87
- headers: { 'Content-Type': 'application/json' },
88
- body: JSON.stringify(credentials)
82
+ login: async (credentials) => {
83
+ set({ isLoading: true, error: null })
84
+ try {
85
+ const response = await fetch('/api/auth/login', {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify(credentials)
89
89
  })
90
90
 
91
91
  if (!response.ok) {
@@ -108,13 +108,13 @@ export function createUserStore(options: StoreOptions<BaseUserStore> = {}) {
108
108
  }
109
109
  },
110
110
 
111
- register: async (data) => {
112
- set({ isLoading: true, error: null })
113
- try {
114
- const response = await fetch('/api/auth/register', {
115
- method: 'POST',
116
- headers: { 'Content-Type': 'application/json' },
117
- body: JSON.stringify(data)
111
+ register: async (data) => {
112
+ set({ isLoading: true, error: null })
113
+ try {
114
+ const response = await fetch('/api/auth/register', {
115
+ method: 'POST',
116
+ headers: { 'Content-Type': 'application/json' },
117
+ body: JSON.stringify(data)
118
118
  })
119
119
 
120
120
  if (!response.ok) {
@@ -135,14 +135,14 @@ export function createUserStore(options: StoreOptions<BaseUserStore> = {}) {
135
135
  })
136
136
  throw error
137
137
  }
138
- },
139
-
140
- logout: () => {
141
- // Call logout API
142
- fetch('/api/auth/logout', { method: 'POST' }).catch(console.error)
143
-
144
- set({
145
- currentUser: null,
138
+ },
139
+
140
+ logout: () => {
141
+ // Call logout API
142
+ fetch('/api/auth/logout', { method: 'POST' }).catch(console.error)
143
+
144
+ set({
145
+ currentUser: null,
146
146
  isAuthenticated: false,
147
147
  error: null
148
148
  })
@@ -151,15 +151,15 @@ export function createUserStore(options: StoreOptions<BaseUserStore> = {}) {
151
151
  updateProfile: async (data) => {
152
152
  const { currentUser } = get()
153
153
  if (!currentUser) {
154
- throw new Error('No user logged in')
155
- }
156
-
157
- set({ isLoading: true, error: null })
158
- try {
159
- const response = await fetch('/api/user/profile', {
160
- method: 'PUT',
161
- headers: { 'Content-Type': 'application/json' },
162
- body: JSON.stringify(data)
154
+ throw new Error('No user logged in')
155
+ }
156
+
157
+ set({ isLoading: true, error: null })
158
+ try {
159
+ const response = await fetch('/api/user/profile', {
160
+ method: 'PUT',
161
+ headers: { 'Content-Type': 'application/json' },
162
+ body: JSON.stringify(data)
163
163
  })
164
164
 
165
165
  if (!response.ok) {
@@ -183,11 +183,11 @@ export function createUserStore(options: StoreOptions<BaseUserStore> = {}) {
183
183
 
184
184
  clearError: () => set({ error: null }),
185
185
  setLoading: (loading) => set({ isLoading: loading })
186
- }),
187
- {
188
- name: 'user-store',
189
- persist: true,
190
- ...options
191
- }
192
- )
193
- }
186
+ }),
187
+ {
188
+ name: 'user-store',
189
+ persist: true,
190
+ ...options
191
+ }
192
+ )
193
+ }
@@ -8,8 +8,8 @@ export {
8
8
  createUserStore
9
9
  } from './createStore'
10
10
 
11
- export type {
12
- StoreOptions,
13
- BaseUser,
14
- BaseUserStore
15
- } from './createStore'
11
+ export type {
12
+ StoreOptions,
13
+ BaseUser,
14
+ BaseUserStore
15
+ } from './createStore'
@@ -1,6 +1,6 @@
1
1
  // Auto-generated Live Components Registration
2
2
  // Generated by @fluxstack/live — DO NOT EDIT MANUALLY
3
- // Generated at: 2026-03-21T23:18:32.007Z
3
+ // Generated at: 2026-04-15T23:06:28.948Z
4
4
 
5
5
  import { LiveAdminPanel } from "@app/server/live/LiveAdminPanel"
6
6
  import { LiveCounter } from "@app/server/live/LiveCounter"
@@ -10,7 +10,6 @@ import { LivePingPong } from "@app/server/live/LivePingPong"
10
10
  import { LiveProtectedChat } from "@app/server/live/LiveProtectedChat"
11
11
  import { LiveRoomChat } from "@app/server/live/LiveRoomChat"
12
12
  import { LiveSharedCounter } from "@app/server/live/LiveSharedCounter"
13
- import { LiveUpload } from "@app/server/live/LiveUpload"
14
13
 
15
14
  // Component classes array for LiveServer({ components }) option
16
15
  export const liveComponentClasses = [
@@ -22,5 +21,4 @@ export const liveComponentClasses = [
22
21
  LiveProtectedChat,
23
22
  LiveRoomChat,
24
23
  LiveSharedCounter,
25
- LiveUpload,
26
24
  ]
@@ -3,4 +3,4 @@
3
3
  * Single source of truth for version number
4
4
  * Auto-synced with package.json
5
5
  */
6
- export const FLUXSTACK_VERSION = '1.18.1'
6
+ export const FLUXSTACK_VERSION = '1.21.0'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fluxstack",
3
- "version": "1.20.0",
3
+ "version": "1.21.0",
4
4
  "description": "⚡ Revolutionary full-stack TypeScript framework with Declarative Config System, Elysia + React + Bun",
5
5
  "keywords": [
6
6
  "framework",
package/tsconfig.json CHANGED
@@ -44,9 +44,10 @@
44
44
  },
45
45
  "exclude": [
46
46
  "examples/**/*",
47
- "**/*.test.ts",
48
- "**/__tests__/**/*",
49
- "run-env-tests.ts",
50
- "plugins/**/scripts/**/*"
51
- ]
52
- }
47
+ "**/*.test.ts",
48
+ "**/__tests__/**/*",
49
+ "dist/**/*",
50
+ "run-env-tests.ts",
51
+ "plugins/**/scripts/**/*"
52
+ ]
53
+ }
@@ -1,200 +0,0 @@
1
- import { useEffect, useMemo, useState } from 'react'
2
- import { useLiveChunkedUpload } from '@/core/client'
3
- import type { LiveChunkedUploadOptions } from '@/core/client'
4
- import type { FileUploadCompleteResponse } from '@core/types/types'
5
- import { LiveUpload } from '@server/live/LiveUpload'
6
-
7
- // Derive the state type from the actual LiveUpload component to avoid duplication
8
- type LiveUploadState = typeof LiveUpload.defaultState
9
-
10
- // Minimal interface for any Live.use() proxy compatible with LiveUpload
11
- interface LiveUploadProxy {
12
- $componentId: string | null
13
- $connected: boolean
14
- $state: LiveUploadState
15
- $error?: string | null
16
- startUpload: (payload: { fileName: string; fileSize: number; fileType: string }) => Promise<any>
17
- updateProgress: (payload: { progress: number; bytesUploaded: number; totalBytes: number }) => Promise<any>
18
- completeUpload: (payload: { fileUrl: string }) => Promise<any>
19
- failUpload: (payload: { error: string }) => Promise<any>
20
- reset: () => Promise<any>
21
- }
22
-
23
- export interface LiveUploadWidgetProps {
24
- live: LiveUploadProxy
25
- title?: string
26
- description?: string
27
- allowPreview?: boolean
28
- options?: LiveChunkedUploadOptions
29
- onComplete?: (response: FileUploadCompleteResponse) => void
30
- }
31
-
32
- export function LiveUploadWidget({
33
- live,
34
- title = 'Upload em Chunks',
35
- description = 'Envio via WebSocket com Live Components e reatividade server-side.',
36
- allowPreview = true,
37
- options,
38
- onComplete
39
- }: LiveUploadWidgetProps) {
40
- // live is expected to be a LiveUpload-compatible component
41
- const [selectedFile, setSelectedFile] = useState<File | null>(null)
42
- const [localError, setLocalError] = useState<string | null>(null)
43
- const [previewUrl, setPreviewUrl] = useState<string | null>(null)
44
-
45
- const mergedOptions = useMemo<LiveChunkedUploadOptions>(() => {
46
- return {
47
- allowedTypes: [],
48
- maxFileSize: 500 * 1024 * 1024,
49
- adaptiveChunking: true,
50
- fileUrlResolver: (fileUrl) => fileUrl.startsWith('/uploads/') ? `/api${fileUrl}` : fileUrl,
51
- onComplete,
52
- ...options
53
- }
54
- }, [options, onComplete])
55
-
56
- const {
57
- uploading,
58
- bytesUploaded,
59
- totalBytes,
60
- uploadFile,
61
- cancelUpload,
62
- reset
63
- } = useLiveChunkedUpload(live, mergedOptions)
64
-
65
- const canUpload = live.$connected && !!live.$componentId && !uploading
66
-
67
- const handleSelectFile = (e: React.ChangeEvent<HTMLInputElement>) => {
68
- const file = e.target.files?.[0] ?? null
69
- setSelectedFile(file)
70
- setLocalError(null)
71
-
72
- if (previewUrl) {
73
- URL.revokeObjectURL(previewUrl)
74
- setPreviewUrl(null)
75
- }
76
-
77
- if (allowPreview && file && file.type.startsWith('image/')) {
78
- setPreviewUrl(URL.createObjectURL(file))
79
- }
80
- }
81
-
82
- useEffect(() => {
83
- return () => {
84
- if (previewUrl) {
85
- URL.revokeObjectURL(previewUrl)
86
- }
87
- }
88
- }, [previewUrl])
89
-
90
- const handleStartUpload = async () => {
91
- if (!selectedFile) {
92
- setLocalError('Selecione um arquivo primeiro.')
93
- return
94
- }
95
-
96
- if (!live.$connected || !live.$componentId) {
97
- setLocalError('WebSocket ainda nao conectou. Tente novamente em alguns segundos.')
98
- return
99
- }
100
-
101
- setLocalError(null)
102
- await uploadFile(selectedFile)
103
- }
104
-
105
- const handleReset = async () => {
106
- setSelectedFile(null)
107
- setLocalError(null)
108
- if (previewUrl) {
109
- URL.revokeObjectURL(previewUrl)
110
- setPreviewUrl(null)
111
- }
112
- await reset()
113
- }
114
-
115
- const resolvedUrl = live.$state.fileUrl
116
-
117
- return (
118
- <div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-xl w-full mx-auto">
119
- <h2 className="text-2xl font-bold text-white mb-2 text-center">
120
- {title}
121
- </h2>
122
-
123
- <p className="text-gray-400 text-sm text-center mb-6">
124
- {description}
125
- </p>
126
-
127
- <div className="space-y-4">
128
- <input
129
- type="file"
130
- onChange={handleSelectFile}
131
- className="w-full text-sm text-gray-300 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:bg-white/10 file:text-white hover:file:bg-white/20"
132
- disabled={!live.$connected || uploading}
133
- />
134
-
135
- <div className="flex gap-3">
136
- <button
137
- onClick={handleStartUpload}
138
- disabled={!canUpload || !selectedFile}
139
- className="flex-1 px-4 py-2 rounded-lg bg-emerald-500/20 border border-emerald-500/30 text-emerald-300 hover:bg-emerald-500/30 transition-all disabled:opacity-50"
140
- >
141
- Iniciar Upload
142
- </button>
143
- <button
144
- onClick={cancelUpload}
145
- disabled={!uploading}
146
- className="px-4 py-2 rounded-lg bg-red-500/20 border border-red-500/30 text-red-300 hover:bg-red-500/30 transition-all disabled:opacity-50"
147
- >
148
- Cancelar
149
- </button>
150
- <button
151
- onClick={handleReset}
152
- className="px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white hover:bg-white/20 transition-all"
153
- >
154
- Reset
155
- </button>
156
- </div>
157
-
158
- {(localError || live.$state.error || live.$error) && (
159
- <div className="text-sm text-red-300 bg-red-500/10 border border-red-500/30 rounded-lg px-3 py-2">
160
- {localError || live.$state.error || live.$error}
161
- </div>
162
- )}
163
-
164
- <div className="bg-black/40 border border-white/10 rounded-xl p-4">
165
- <div className="flex justify-between text-xs text-gray-400 mb-2">
166
- <span>Status: {live.$state.status}</span>
167
- <span>{Math.round(live.$state.progress)}%</span>
168
- </div>
169
- <div className="w-full h-3 bg-white/10 rounded-full overflow-hidden">
170
- <div
171
- className="h-full bg-theme-gradient transition-all"
172
- style={{ width: `${live.$state.progress}%` }}
173
- />
174
- </div>
175
- <div className="flex justify-between text-xs text-gray-500 mt-2">
176
- <span>{live.$state.fileName || 'Nenhum arquivo selecionado'}</span>
177
- <span>{bytesUploaded > 0 ? `${Math.round(bytesUploaded / 1024)} KB` : ''}{totalBytes > 0 ? ` / ${Math.round(totalBytes / 1024)} KB` : ''}</span>
178
- </div>
179
- </div>
180
-
181
- {previewUrl && (
182
- <div className="bg-white/5 border border-white/10 rounded-xl p-4">
183
- <div className="text-xs text-gray-400 mb-2">Preview</div>
184
- <img
185
- src={previewUrl}
186
- alt="Preview"
187
- className="max-h-48 w-full object-contain rounded-lg border border-white/10"
188
- />
189
- </div>
190
- )}
191
-
192
- {resolvedUrl && live.$state.status === 'complete' && (
193
- <div className="bg-emerald-500/10 border border-emerald-500/30 rounded-xl p-4 text-sm text-emerald-200">
194
- Upload concluido: <a className="underline" href={resolvedUrl} target="_blank" rel="noopener noreferrer">abrir arquivo</a>
195
- </div>
196
- )}
197
- </div>
198
- </div>
199
- )
200
- }
@@ -1,21 +0,0 @@
1
- import { useState } from 'react'
2
- import { LiveUploadWidget } from '../components/LiveUploadWidget'
3
- import { useLiveUpload } from '@/core/client'
4
-
5
- export function UploadDemo() {
6
- const [lastUrl, setLastUrl] = useState<string | null>(null)
7
- const { live } = useLiveUpload({
8
- onComplete: (response) => setLastUrl(response.fileUrl || null)
9
- })
10
-
11
- return (
12
- <div className="flex flex-col gap-4 max-w-xl w-full mx-auto">
13
- <LiveUploadWidget live={live} />
14
- {lastUrl && (
15
- <div className="text-xs text-gray-500 text-center">
16
- Ultimo arquivo: {lastUrl}
17
- </div>
18
- )}
19
- </div>
20
- )
21
- }
@@ -1,96 +0,0 @@
1
- // LiveUpload - Estado de upload chunked + sincronização UI
2
-
3
- import { LiveComponent } from '@core/types/types'
4
-
5
- // Componente Cliente (Ctrl+Click para navegar)
6
- import type { UploadDemo as _Client } from '@client/src/live/UploadDemo'
7
-
8
- export class LiveUpload extends LiveComponent<typeof LiveUpload.defaultState> {
9
- static componentName = 'LiveUpload'
10
- static publicActions = ['startUpload', 'updateProgress', 'completeUpload', 'failUpload', 'reset'] as const
11
- static defaultState = {
12
- status: 'idle' as 'idle' | 'uploading' | 'complete' | 'error',
13
- progress: 0,
14
- fileName: '',
15
- fileSize: 0,
16
- fileType: '',
17
- fileUrl: '',
18
- bytesUploaded: 0,
19
- totalBytes: 0,
20
- error: null as string | null
21
- }
22
-
23
- async startUpload(payload: { fileName: string; fileSize: number; fileType: string }) {
24
- const fileName = payload.fileName
25
-
26
- // Validate filename length
27
- if (!fileName || fileName.length > 255) {
28
- throw new Error('Invalid file name: must be 1-255 characters')
29
- }
30
-
31
- // Block path traversal, null bytes, and control characters
32
- if (/[\x00-\x1f]/.test(fileName) || fileName.includes('..') || fileName.includes('/') || fileName.includes('\\')) {
33
- throw new Error('Invalid file name: contains forbidden characters')
34
- }
35
-
36
- // Block Windows reserved names
37
- const baseName = fileName.split('.')[0].toUpperCase()
38
- const reserved = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'LPT1', 'LPT2', 'LPT3']
39
- if (reserved.includes(baseName)) {
40
- throw new Error('Invalid file name: reserved name')
41
- }
42
-
43
- // All file types allowed - no extension blocking
44
- // Security note: Configure allowed extensions per your application needs
45
-
46
- this.setState({
47
- status: 'uploading',
48
- progress: 0,
49
- fileName: payload.fileName,
50
- fileSize: payload.fileSize,
51
- fileType: payload.fileType,
52
- fileUrl: '',
53
- bytesUploaded: 0,
54
- totalBytes: payload.fileSize,
55
- error: null
56
- })
57
-
58
- return { success: true }
59
- }
60
-
61
- async updateProgress(payload: { progress: number; bytesUploaded: number; totalBytes: number }) {
62
- const progress = Math.max(0, Math.min(100, payload.progress))
63
- this.setState({
64
- progress,
65
- bytesUploaded: payload.bytesUploaded,
66
- totalBytes: payload.totalBytes
67
- })
68
-
69
- return { success: true, progress }
70
- }
71
-
72
- async completeUpload(payload: { fileUrl: string }) {
73
- this.setState({
74
- status: 'complete',
75
- progress: 100,
76
- fileUrl: payload.fileUrl,
77
- error: null
78
- })
79
-
80
- return { success: true }
81
- }
82
-
83
- async failUpload(payload: { error: string }) {
84
- this.setState({
85
- status: 'error',
86
- error: payload.error || 'Upload failed'
87
- })
88
-
89
- return { success: true }
90
- }
91
-
92
- async reset() {
93
- this.setState({ ...LiveUpload.defaultState })
94
- return { success: true }
95
- }
96
- }
@@ -1,70 +0,0 @@
1
- import { useMemo } from 'react'
2
- import { Live, useLiveChunkedUpload } from '@fluxstack/live-react'
3
- import type { LiveChunkedUploadOptions } from '@fluxstack/live-react'
4
- import type { FileUploadCompleteResponse } from '@fluxstack/live'
5
- import { LiveUpload } from '@server/live/LiveUpload'
6
-
7
- export interface UseLiveUploadOptions {
8
- live?: {
9
- room?: string
10
- userId?: string
11
- autoMount?: boolean
12
- debug?: boolean
13
- }
14
- upload?: LiveChunkedUploadOptions
15
- onProgress?: (progress: number, bytesUploaded: number, totalBytes: number) => void
16
- onComplete?: (response: FileUploadCompleteResponse) => void
17
- onError?: (error: string) => void
18
- }
19
-
20
- export function useLiveUpload(options: UseLiveUploadOptions = {}) {
21
- const { live: liveOptions, upload: uploadOptions, onProgress, onComplete, onError } = options
22
-
23
- const live = Live.use(LiveUpload, {
24
- initialState: LiveUpload.defaultState,
25
- ...liveOptions
26
- })
27
-
28
- const mergedUploadOptions = useMemo<LiveChunkedUploadOptions>(() => {
29
- return {
30
- allowedTypes: [],
31
- maxFileSize: 500 * 1024 * 1024,
32
- adaptiveChunking: true,
33
- fileUrlResolver: (fileUrl) => fileUrl.startsWith('/uploads/') ? `/api${fileUrl}` : fileUrl,
34
- onProgress,
35
- onComplete,
36
- onError,
37
- ...uploadOptions
38
- }
39
- }, [onProgress, onComplete, onError, uploadOptions])
40
-
41
- const upload = useLiveChunkedUpload(live, mergedUploadOptions)
42
-
43
- const startUpload = useMemo(() => {
44
- return async (file: File) => {
45
- if (!live.$connected || !live.$componentId) {
46
- const msg = 'WebSocket nao conectado. Tente novamente.'
47
- onError?.(msg)
48
- await live.failUpload({ error: msg })
49
- return
50
- }
51
- await upload.uploadFile(file)
52
- }
53
- }, [live, upload, onError])
54
-
55
- return {
56
- live,
57
- state: live.$state,
58
- status: live.$state.status,
59
- connected: live.$connected,
60
- componentId: live.$componentId,
61
- uploading: upload.uploading,
62
- progress: upload.progress,
63
- bytesUploaded: upload.bytesUploaded,
64
- totalBytes: upload.totalBytes,
65
- error: live.$state.error,
66
- startUpload,
67
- cancelUpload: upload.cancelUpload,
68
- reset: upload.reset
69
- }
70
- }