gongxiangp2/lib/material-upload-chunk-policy.ts
chenpei 7326e273e1 fix: harden media upload and release safety
Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
2026-08-12 18:04:08 +08:00

78 lines
2.1 KiB
TypeScript

const MEBIBYTE = 1024 * 1024
export const MAX_LOGICAL_UPLOAD_BYTES = 10 * 1024 * MEBIBYTE
export const MAX_UPLOAD_CHUNK_BYTES = 20 * MEBIBYTE
export const MAX_UPLOAD_CHUNKS = Math.ceil(MAX_LOGICAL_UPLOAD_BYTES / (10 * MEBIBYTE))
const LEGACY_BROWSER_CHUNK_SIZES = [10 * MEBIBYTE, 20 * MEBIBYTE]
/**
* Resolve and validate the physical chunk size used for one upload session.
* Older already-open browser pages did not send chunkSize to init; only the two
* historically supported 10/20 MiB layouts are inferred for those sessions.
*/
export function resolveUploadChunkSize(
fileSize: number,
totalChunks: number,
rawChunkSize?: unknown,
): number | null {
if (
!Number.isSafeInteger(fileSize)
|| fileSize <= 0
|| fileSize > MAX_LOGICAL_UPLOAD_BYTES
|| !Number.isInteger(totalChunks)
|| totalChunks <= 0
|| totalChunks > MAX_UPLOAD_CHUNKS
) {
return null
}
const hasExplicitChunkSize = rawChunkSize !== undefined
&& rawChunkSize !== null
&& String(rawChunkSize).trim() !== ''
if (hasExplicitChunkSize) {
const chunkSize = Number(rawChunkSize)
if (
!Number.isSafeInteger(chunkSize)
|| chunkSize <= 0
|| chunkSize > MAX_UPLOAD_CHUNK_BYTES
|| Math.ceil(fileSize / chunkSize) !== totalChunks
) {
return null
}
return chunkSize
}
return LEGACY_BROWSER_CHUNK_SIZES.find(
(chunkSize) => Math.ceil(fileSize / chunkSize) === totalChunks,
) || null
}
export function getExpectedUploadChunkBytes(input: {
fileSize: number
totalChunks: number
chunkSize: number
chunkIndex: number
}): number | null {
const { fileSize, totalChunks, chunkSize, chunkIndex } = input
if (
!Number.isInteger(chunkIndex)
|| chunkIndex < 0
|| chunkIndex >= totalChunks
|| resolveUploadChunkSize(fileSize, totalChunks, chunkSize) !== chunkSize
) {
return null
}
const expectedBytes = chunkIndex === totalChunks - 1
? fileSize - chunkSize * (totalChunks - 1)
: chunkSize
return Number.isSafeInteger(expectedBytes)
&& expectedBytes > 0
&& expectedBytes <= MAX_UPLOAD_CHUNK_BYTES
? expectedBytes
: null
}