minor fixes

This commit is contained in:
2026-04-09 08:50:36 +02:00
parent 23d363a50c
commit a2457f4e49
75 changed files with 3848 additions and 387 deletions

View File

@@ -17,6 +17,19 @@ const phases = {
error: 'error',
}
const DEFAULT_CHUNK_REQUEST_TIMEOUT_MS = 45000
const MIN_CHUNK_SIZE_BYTES = 256 * 1024
function formatChunkSize(bytes) {
if (!Number.isFinite(bytes) || bytes <= 0) return '0 KB'
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(bytes % (1024 * 1024) === 0 ? 0 : 1)} MB`
return `${Math.max(1, Math.round(bytes / 1024))} KB`
}
function isRequestTooLarge(error) {
return Number(error?.response?.status || 0) === 413
}
const initialState = {
phase: phases.idle,
sessionId: null,
@@ -163,9 +176,14 @@ function getTypeKey(ct) {
return String(ct.name || '').toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '')
}
function useUploadMachine({ draftId, filesCdnUrl, chunkSize, userId }) {
function useUploadMachine({ draftId, filesCdnUrl, chunkSize, chunkRequestTimeoutMs, userId }) {
const [state, dispatch] = useReducer(reducer, { ...initialState, draftId })
const pollRef = useRef(null)
const adaptiveChunkSizeRef = useRef(Math.max(1, Number(chunkSize || 0)))
const effectiveChunkRequestTimeoutMs = (() => {
const parsed = Number(chunkRequestTimeoutMs)
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : DEFAULT_CHUNK_REQUEST_TIMEOUT_MS
})()
const extractErrorMessage = useCallback((error, fallback) => {
const message = error?.response?.data?.message
@@ -379,6 +397,7 @@ function useUploadMachine({ draftId, filesCdnUrl, chunkSize, userId }) {
try {
const res = await window.axios.post('/api/uploads/chunk', payload, {
timeout: effectiveChunkRequestTimeoutMs,
headers: uploadToken ? { 'X-Upload-Token': uploadToken } : undefined,
})
@@ -389,13 +408,16 @@ function useUploadMachine({ draftId, filesCdnUrl, chunkSize, userId }) {
return data
} catch (error) {
if (isRequestTooLarge(error)) {
throw error
}
if (attempt < MAX_CHUNK_RETRIES) {
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS * (attempt + 1)))
return uploadChunk(sessionId, uploadToken, blob, offset, totalSize, attempt + 1)
}
throw error
}
}, [])
}, [effectiveChunkRequestTimeoutMs])
const uploadFile = useCallback(async (sessionId, uploadToken, file) => {
dispatch({ type: 'UPLOAD_START' })
@@ -415,7 +437,8 @@ function useUploadMachine({ draftId, filesCdnUrl, chunkSize, userId }) {
if (offset > totalSize) offset = 0
while (offset < totalSize) {
const nextOffset = Math.min(offset + chunkSize, totalSize)
const activeChunkSize = Math.max(MIN_CHUNK_SIZE_BYTES, Number(adaptiveChunkSizeRef.current || chunkSize || MIN_CHUNK_SIZE_BYTES))
const nextOffset = Math.min(offset + activeChunkSize, totalSize)
const chunk = file.slice(offset, nextOffset)
try {
@@ -425,6 +448,14 @@ function useUploadMachine({ draftId, filesCdnUrl, chunkSize, userId }) {
offset = nextOffset
}
} catch (error) {
if (isRequestTooLarge(error) && activeChunkSize > MIN_CHUNK_SIZE_BYTES) {
const nextChunkSize = Math.max(MIN_CHUNK_SIZE_BYTES, Math.floor(activeChunkSize / 2))
if (nextChunkSize < activeChunkSize) {
adaptiveChunkSizeRef.current = nextChunkSize
pushNotice('warning', `Server rejected ${formatChunkSize(activeChunkSize)} chunks. Retrying with ${formatChunkSize(nextChunkSize)} chunks.`)
continue
}
}
const notice = mapUploadErrorNotice(error, 'File upload failed. Please retry.')
dispatch({ type: 'UPLOAD_ERROR', error: notice.message })
pushMappedNotice(notice)
@@ -587,7 +618,7 @@ function useUploadMachine({ draftId, filesCdnUrl, chunkSize, userId }) {
}
}
export default function UploadPage({ draftId, filesCdnUrl, chunkSize }) {
export default function UploadPage({ draftId, filesCdnUrl, chunkSize, chunkRequestTimeoutMs }) {
const { props } = usePage()
const windowFlags = window?.SKINBASE_FLAGS || {}
@@ -618,6 +649,7 @@ export default function UploadPage({ draftId, filesCdnUrl, chunkSize }) {
<UploadWizard
initialDraftId={draftId ?? null}
chunkSize={chunkSize}
chunkRequestTimeoutMs={chunkRequestTimeoutMs}
contentTypes={Array.isArray(props?.content_types) ? props.content_types : []}
suggestedTags={Array.isArray(props?.suggested_tags) ? props.suggested_tags : []}
groupOptions={Array.isArray(props?.group_options) ? props.group_options : []}
@@ -689,7 +721,13 @@ export default function UploadPage({ draftId, filesCdnUrl, chunkSize }) {
const userId = props?.auth?.user?.id ?? null
const suggestedTags = Array.isArray(props?.suggested_tags) ? props.suggested_tags : []
const safeChunkSize = Math.max(1, Number(chunkSize || 0))
const { state, dispatch, previewUrl, startUpload, cancelUpload } = useUploadMachine({ draftId, filesCdnUrl, chunkSize: safeChunkSize, userId })
const { state, dispatch, previewUrl, startUpload, cancelUpload } = useUploadMachine({
draftId,
filesCdnUrl,
chunkSize: safeChunkSize,
chunkRequestTimeoutMs,
userId,
})
const fileInputRef = useRef(null)
const [confirmCancel, setConfirmCancel] = useState(false)
const [contentTypes, setContentTypes] = useState([])