/** * ZIP file creation utility for ComfyUI Viewer * Creates ZIP blobs without external dependencies */ /** * Create a ZIP blob from an array of files * @param {Array<{name: string, content: string}>} files - Files to include * @returns {Promise} ZIP blob */ export async function createZipBlob(files) { const crc32 = (data) => { let crc = 0xFFFFFFFF; const table = new Uint32Array(256); for (let i = 0; i < 256; i++) { let c = i; for (let j = 0; j < 8; j++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); table[i] = c; } for (let i = 0; i < data.length; i++) crc = table[(crc ^ data[i]) & 0xFF] ^ (crc >>> 8); return (crc ^ 0xFFFFFFFF) >>> 0; }; const encoder = new TextEncoder(); const parts = []; const centralDir = []; let offset = 0; for (const file of files) { const nameBytes = encoder.encode(file.name); const contentBytes = encoder.encode(file.content); const crc = crc32(contentBytes); const localHeader = new Uint8Array(30 + nameBytes.length); const view = new DataView(localHeader.buffer); view.setUint32(0, 0x04034B50, true); view.setUint16(4, 20, true); view.setUint16(6, 0, true); view.setUint16(8, 0, true); view.setUint16(10, 0, true); view.setUint16(12, 0, true); view.setUint32(14, crc, true); view.setUint32(18, contentBytes.length, true); view.setUint32(22, contentBytes.length, true); view.setUint16(26, nameBytes.length, true); view.setUint16(28, 0, true); localHeader.set(nameBytes, 30); const centralEntry = new Uint8Array(46 + nameBytes.length); const cView = new DataView(centralEntry.buffer); cView.setUint32(0, 0x02014B50, true); cView.setUint16(4, 20, true); cView.setUint16(6, 20, true); cView.setUint16(8, 0, true); cView.setUint16(10, 0, true); cView.setUint16(12, 0, true); cView.setUint16(14, 0, true); cView.setUint32(16, crc, true); cView.setUint32(20, contentBytes.length, true); cView.setUint32(24, contentBytes.length, true); cView.setUint16(28, nameBytes.length, true); cView.setUint16(30, 0, true); cView.setUint16(32, 0, true); cView.setUint16(34, 0, true); cView.setUint16(36, 0, true); cView.setUint32(38, 0, true); cView.setUint32(42, offset, true); centralEntry.set(nameBytes, 46); parts.push(localHeader, contentBytes); centralDir.push(centralEntry); offset += localHeader.length + contentBytes.length; } const centralDirOffset = offset; let centralDirSize = 0; for (const entry of centralDir) { parts.push(entry); centralDirSize += entry.length; } const endRecord = new Uint8Array(22); const endView = new DataView(endRecord.buffer); endView.setUint32(0, 0x06054B50, true); endView.setUint16(4, 0, true); endView.setUint16(6, 0, true); endView.setUint16(8, files.length, true); endView.setUint16(10, files.length, true); endView.setUint32(12, centralDirSize, true); endView.setUint32(16, centralDirOffset, true); endView.setUint16(20, 0, true); parts.push(endRecord); return new Blob(parts, { type: "application/zip" }); }