/** Chunk size used when rebuilding attachment blobs from disk without blocking the UI thread. */ export const ATTACHMENT_BLOB_READ_CHUNK_SIZE_BYTES = 256 * 1024; /** Number of bytes encoded per chunk to keep base64 encoding off the call stack. */ const BASE64_ENCODE_CHUNK_SIZE = 0x8000; /** Decode a base64 payload into bytes for Blob construction. */ export function decodeBase64ToUint8Array(base64: string): Uint8Array { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let index = 0; index < binary.length; index++) { bytes[index] = binary.charCodeAt(index); } return bytes; } /** Encode bytes into a base64 payload, chunked so large buffers cannot overflow the call stack. */ export function encodeUint8ArrayToBase64(bytes: Uint8Array): string { let binary = ''; for (let offset = 0; offset < bytes.length; offset += BASE64_ENCODE_CHUNK_SIZE) { const chunk = bytes.subarray(offset, offset + BASE64_ENCODE_CHUNK_SIZE); binary += String.fromCharCode(...chunk); } return btoa(binary); } /** Returns the decoded byte length of a base64 payload without allocating the bytes. */ export function base64DecodedByteLength(base64: string): number { const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0; return Math.max(0, Math.floor((base64.length * 3) / 4) - padding); } /** Yield control back to the browser so long attachment hydration cannot freeze Electron. */ export function yieldToAttachmentHydrationLoop(): Promise { return new Promise((resolve) => { setTimeout(resolve, 0); }); }