{"record":{"id":"f27ca79b9cb61985","repo":"siyuan-note/siyuan","slug":"base64-image-size-limit","errorCode":"BASE64_IMAGE_SIZE_LIMIT","errorMessage":"Base64 image item size ${actualBytes} exceeds ${itemMaxBytes} bytes","messagePattern":"Base64 image item size (.+?) exceeds (.+?) bytes","errorType":"validation","errorClass":"Base64ImageSizeLimitError","httpStatus":null,"severity":"error","filePath":"app/src/protyle/upload/base64File.ts","lineNumber":26,"sourceCode":"\nexport type TBase64ImageSizeLimitScope = \"item\" | \"batch\";\n\nexport class Base64ImageSizeLimitError extends Error {\n    readonly code = \"BASE64_IMAGE_SIZE_LIMIT\";\n\n    constructor(readonly scope: TBase64ImageSizeLimitScope, readonly actualBytes: number, readonly maxBytes: number) {\n        super(`Base64 image ${scope} size ${actualBytes} exceeds ${maxBytes} bytes`);\n        this.name = \"Base64ImageSizeLimitError\";\n    }\n}\n\nexport const isBase64ImageSizeLimitError = (error: unknown): error is Base64ImageSizeLimitError =>\n    error instanceof Base64ImageSizeLimitError;\n\nexport const assertBase64ImageItemSize = (actualBytes: number, maxBytes?: number) => {\n    const itemMaxBytes = Math.min(maxBytes ?? BASE64_IMAGE_ITEM_MAX_BYTES, BASE64_IMAGE_ITEM_MAX_BYTES);\n    if (actualBytes > itemMaxBytes) {\n        throw new Base64ImageSizeLimitError(\"item\", actualBytes, itemMaxBytes);\n    }\n};\n\nexport const addBase64ImageBatchSize = (currentBytes: number, fileBytes: number) => {\n    const totalBytes = currentBytes + fileBytes;\n    if (totalBytes > BASE64_IMAGE_BATCH_MAX_BYTES) {\n        throw new Base64ImageSizeLimitError(\"batch\", totalBytes, BASE64_IMAGE_BATCH_MAX_BYTES);\n    }\n    return totalBytes;\n};\n\nconst startsWith = (bytes: Uint8Array, signature: number[]) =>\n    signature.every((value, index) => bytes[index] === value);\n\nconst detectBase64ImageFormat = (bytes: Uint8Array): IBase64ImageFormat | undefined => {\n    if (startsWith(bytes, [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {\n        return {extension: \"png\", mime: \"image/png\"};\n    }","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/app/src/protyle/upload/base64File.ts#L8-L44","documentation":"assertBase64ImageItemSize enforces a per-image byte limit when handling base64 images. The effective maximum is the caller-supplied maxBytes capped at BASE64_IMAGE_ITEM_MAX_BYTES. When the decoded image's byte size (actualBytes) exceeds that cap, a Base64ImageSizeLimitError (scope 'item', code BASE64_IMAGE_SIZE_LIMIT) is thrown with the message 'Base64 image item size ${actualBytes} exceeds ${itemMaxBytes} bytes'. It is called from createBase64ImageFile before constructing the File.","triggerScenarios":"Call createBase64ImageFile (or directly assertBase64ImageItemSize) with a base64 data URL whose decoded payload is larger than BASE64_IMAGE_ITEM_MAX_BYTES (or the caller's smaller maxBytes override).","commonSituations":"Pasting or dropping a very large screenshot/clipboard image that gets base64-encoded; a plugin or script uploading oversized images via the base64 path; tightened maxBytes overrides making previously acceptable images too large.","solutions":["Decode the base64 first and check its size with a helper (base64 length * 3/4 minus padding) before calling, then downscale/recompress the image if over the limit","Compress the image (e.g. canvas.toBlob at lower quality or rescale dimensions) and retry with the smaller payload","Raise the caller-provided maxBytes only up to BASE64_IMAGE_ITEM_MAX_BYTES if the limit was set too strictly for your workflow","Catch Base64ImageSizeLimitError (or use isBase64ImageSizeLimitError) and surface a user-facing message asking for a smaller image"],"exampleFix":"// before: oversized base64 hits the limit\nawait createBase64ImageFile(dataUrl);\n// after: pre-check and compress if needed\nconst bytes = estimateBase64Bytes(dataUrl);\nif (bytes > BASE64_IMAGE_ITEM_MAX_BYTES) {\n    dataUrl = await compressImage(dataUrl, BASE64_IMAGE_ITEM_MAX_BYTES);\n}\nawait createBase64ImageFile(dataUrl);","handlingStrategy":"validation","validationCode":"// estimate decoded byte size of a base64 data URL before calling\nconst estimateBase64Bytes = (dataUrl: string) => {\n    const base64 = dataUrl.split(\",\")[1] || \"\";\n    const padding = base64.endsWith(\"==\") ? 2 : base64.endsWith(\"=\") ? 1 : 0;\n    return Math.max(0, Math.floor(base64.length * 3 / 4) - padding);\n};\nif (estimateBase64Bytes(dataUrl) > BASE64_IMAGE_ITEM_MAX_BYTES) {\n    // compress or reject before createBase64ImageFile\n}","typeGuard":"import { isBase64ImageSizeLimitError } from \"./base64File\";\nif (isBase64ImageSizeLimitError(err) && err.scope === \"item\") { /* handle per-item limit */ }","tryCatchPattern":"try {\n    await createBase64ImageFile(dataUrl);\n} catch (err) {\n    if (isBase64ImageSizeLimitError(err)) {\n        showToast(`Image too large: ${err.actualBytes} bytes (max ${err.maxBytes})`);\n    } else { throw err; }\n}","preventionTips":["Always estimate decoded size from base64 length before submitting","Downscale clipboard/pasted screenshots to a max dimension before encoding","Compress with canvas.toBlob (JPEG/WebP) for photos; PNG screenshots grow quickly","Never assume a caller-supplied maxBytes can exceed BASE64_IMAGE_ITEM_MAX_BYTES — it is capped"],"tags":["upload","base64","image","size-limit"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}