Files
NetMesh/lib/textZip.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

111 lines
3.5 KiB
TypeScript

export interface TextZipFile {
name: string;
content: string;
}
const ZIP_UTF8_FLAG = 0x0800;
const crcTable = Array.from({ length: 256 }, (_, index) => {
let value = index;
for (let bit = 0; bit < 8; bit += 1) {
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
}
return value >>> 0;
});
const crc32 = (bytes: Uint8Array): number => {
let crc = 0xffffffff;
bytes.forEach((byte) => {
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
});
return (crc ^ 0xffffffff) >>> 0;
};
const pushUint16 = (parts: number[], value: number) => {
parts.push(value & 0xff, (value >>> 8) & 0xff);
};
const pushUint32 = (parts: number[], value: number) => {
parts.push(value & 0xff, (value >>> 8) & 0xff, (value >>> 16) & 0xff, (value >>> 24) & 0xff);
};
const concatZipParts = (header: number[], ...parts: Uint8Array[]): Uint8Array => {
const headerBytes = Uint8Array.from(header);
const chunk = new Uint8Array(
headerBytes.length + parts.reduce((total, part) => total + part.length, 0),
);
let offset = 0;
chunk.set(headerBytes, offset);
offset += headerBytes.length;
parts.forEach((part) => {
chunk.set(part, offset);
offset += part.length;
});
return chunk;
};
export const buildTextFilesZipBlob = (files: TextZipFile[]): Blob => {
const encoder = new TextEncoder();
const chunks: Uint8Array[] = [];
const centralDirectory: Uint8Array[] = [];
let offset = 0;
files.forEach((file) => {
const nameBytes = encoder.encode(file.name);
const contentBytes = encoder.encode(file.content);
const checksum = crc32(contentBytes);
const localHeader: number[] = [];
pushUint32(localHeader, 0x04034b50);
pushUint16(localHeader, 20);
pushUint16(localHeader, ZIP_UTF8_FLAG);
pushUint16(localHeader, 0);
pushUint16(localHeader, 0);
pushUint16(localHeader, 0);
pushUint32(localHeader, checksum);
pushUint32(localHeader, contentBytes.length);
pushUint32(localHeader, contentBytes.length);
pushUint16(localHeader, nameBytes.length);
pushUint16(localHeader, 0);
const localChunk = concatZipParts(localHeader, nameBytes, contentBytes);
chunks.push(localChunk);
const centralHeader: number[] = [];
pushUint32(centralHeader, 0x02014b50);
pushUint16(centralHeader, 20);
pushUint16(centralHeader, 20);
pushUint16(centralHeader, ZIP_UTF8_FLAG);
pushUint16(centralHeader, 0);
pushUint16(centralHeader, 0);
pushUint16(centralHeader, 0);
pushUint32(centralHeader, checksum);
pushUint32(centralHeader, contentBytes.length);
pushUint32(centralHeader, contentBytes.length);
pushUint16(centralHeader, nameBytes.length);
pushUint16(centralHeader, 0);
pushUint16(centralHeader, 0);
pushUint16(centralHeader, 0);
pushUint16(centralHeader, 0);
pushUint32(centralHeader, 0);
pushUint32(centralHeader, offset);
centralDirectory.push(concatZipParts(centralHeader, nameBytes));
offset += localChunk.length;
});
const centralDirectorySize = centralDirectory.reduce((total, chunk) => total + chunk.length, 0);
const endRecord: number[] = [];
pushUint32(endRecord, 0x06054b50);
pushUint16(endRecord, 0);
pushUint16(endRecord, 0);
pushUint16(endRecord, files.length);
pushUint16(endRecord, files.length);
pushUint32(endRecord, centralDirectorySize);
pushUint32(endRecord, offset);
pushUint16(endRecord, 0);
return new Blob([...chunks, ...centralDirectory, new Uint8Array(endRecord)], {
type: "application/zip",
});
};