UNPKG

@mindfiredigital/page-builder

Version:
228 lines (227 loc) 6.8 kB
export function createZipFile(files) { // Helper function to convert string to Uint8Array const stringToUint8Array = str => { const encoder = new TextEncoder(); return encoder.encode(str); }; // Generate local file header const createLocalFileHeader = (fileName, fileContent, crc) => { const header = new Uint8Array(30 + fileName.length); // Local file header signature header.set([0x50, 0x4b, 0x03, 0x04]); // Version needed to extract (2.0) header.set([0x14, 0x00], 4); // General purpose bit flag (no compression) header.set([0x00, 0x00], 6); // Compression method (0 = stored) header.set([0x00, 0x00], 8); // Last mod file time (current time) header.set([0x00, 0x00], 10); // Last mod file date (current date) header.set([0x00, 0x00], 12); // CRC-32 header.set( [crc & 0xff, (crc >> 8) & 0xff, (crc >> 16) & 0xff, (crc >> 24) & 0xff], 14 ); // Compressed size header.set( [ fileContent.length & 0xff, (fileContent.length >> 8) & 0xff, (fileContent.length >> 16) & 0xff, (fileContent.length >> 24) & 0xff, ], 18 ); // Uncompressed size header.set( [ fileContent.length & 0xff, (fileContent.length >> 8) & 0xff, (fileContent.length >> 16) & 0xff, (fileContent.length >> 24) & 0xff, ], 22 ); // Filename length header.set([fileName.length & 0xff, (fileName.length >> 8) & 0xff], 26); // Extra field length header.set([0x00, 0x00], 28); // Filename header.set(fileName, 30); return header; }; // Generate central directory header const createCentralDirectoryHeader = ( fileName, fileContent, crc, localHeaderOffset ) => { const header = new Uint8Array(46 + fileName.length); // Central file header signature header.set([0x50, 0x4b, 0x01, 0x02]); // Version made by header.set([0x14, 0x00], 4); // Version needed to extract header.set([0x14, 0x00], 6); // General purpose bit flag header.set([0x00, 0x00], 8); // Compression method (0 = stored) header.set([0x00, 0x00], 10); // Last mod file time header.set([0x00, 0x00], 12); // Last mod file date header.set([0x00, 0x00], 14); // CRC-32 header.set( [crc & 0xff, (crc >> 8) & 0xff, (crc >> 16) & 0xff, (crc >> 24) & 0xff], 16 ); // Compressed size header.set( [ fileContent.length & 0xff, (fileContent.length >> 8) & 0xff, (fileContent.length >> 16) & 0xff, (fileContent.length >> 24) & 0xff, ], 20 ); // Uncompressed size header.set( [ fileContent.length & 0xff, (fileContent.length >> 8) & 0xff, (fileContent.length >> 16) & 0xff, (fileContent.length >> 24) & 0xff, ], 24 ); // Filename length header.set([fileName.length & 0xff, (fileName.length >> 8) & 0xff], 28); // Extra field length header.set([0x00, 0x00], 30); // File comment length header.set([0x00, 0x00], 32); // Disk number start header.set([0x00, 0x00], 34); // Internal file attributes header.set([0x00, 0x00], 36); // External file attributes header.set([0x00, 0x00, 0x00, 0x00], 38); // Relative offset of local header header.set( [ localHeaderOffset & 0xff, (localHeaderOffset >> 8) & 0xff, (localHeaderOffset >> 16) & 0xff, (localHeaderOffset >> 24) & 0xff, ], 42 ); // Filename header.set(fileName, 46); return header; }; // Generate end of central directory record const createEndOfCentralDirectoryRecord = ( fileCount, centralDirectorySize, centralDirectoryOffset ) => { const record = new Uint8Array(22); // End of central directory signature record.set([0x50, 0x4b, 0x05, 0x06]); // Number of this disk record.set([0x00, 0x00], 4); // Disk where central directory starts record.set([0x00, 0x00], 6); // Number of central directory records on this disk record.set([fileCount & 0xff, (fileCount >> 8) & 0xff], 8); // Total number of central directory records record.set([fileCount & 0xff, (fileCount >> 8) & 0xff], 10); // Size of central directory record.set( [ centralDirectorySize & 0xff, (centralDirectorySize >> 8) & 0xff, (centralDirectorySize >> 16) & 0xff, (centralDirectorySize >> 24) & 0xff, ], 12 ); // Offset of central directory record.set( [ centralDirectoryOffset & 0xff, (centralDirectoryOffset >> 8) & 0xff, (centralDirectoryOffset >> 16) & 0xff, (centralDirectoryOffset >> 24) & 0xff, ], 16 ); // Comment length record.set([0x00, 0x00], 20); return record; }; // Combine all parts to create ZIP file const zipParts = []; let currentOffset = 0; const centralDirectoryHeaders = []; // Process each file files.forEach(file => { const fileName = stringToUint8Array(file.name); const fileContent = stringToUint8Array(file.content); // Calculate CRC-32 const fileCrc = crc32(fileContent); // Create local file header const localHeader = createLocalFileHeader(fileName, fileContent, fileCrc); // Add local file header and content to zip parts zipParts.push(localHeader); zipParts.push(fileContent); // Create central directory header const centralHeader = createCentralDirectoryHeader( fileName, fileContent, fileCrc, currentOffset ); centralDirectoryHeaders.push(centralHeader); // Update offset currentOffset += localHeader.length + fileContent.length; }); // Add central directory headers zipParts.push(...centralDirectoryHeaders); // Calculate central directory size const centralDirectorySize = centralDirectoryHeaders.reduce( (sum, header) => sum + header.length, 0 ); // Create and add end of central directory record const endOfCentralDirectory = createEndOfCentralDirectoryRecord( files.length, centralDirectorySize, currentOffset ); zipParts.push(endOfCentralDirectory); // Combine all parts into a single Uint8Array const zipArray = new Uint8Array( zipParts.reduce((acc, part) => acc.concat(Array.from(part)), []) ); // Create and return Blob return new Blob([zipArray], { type: 'application/zip' }); } // CRC-32 implementation function crc32(data) { // Simple CRC-32 implementation let crc = 0xffffffff; for (let i = 0; i < data.length; i++) { crc ^= data[i]; for (let j = 0; j < 8; j++) { crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0); } } return crc ^ 0xffffffff; }