b54a3139c7
Includes QWB, Qortal Web, and Q-Shops Q-Apps with shared packages and build scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
200 lines
5.1 KiB
JavaScript
200 lines
5.1 KiB
JavaScript
/**
|
|
* Bundled to public/zip-store.js (see scripts/bundle-zip-store.mjs).
|
|
* Exposes window.buildZipStore for in-app WEBSITE publish.
|
|
*
|
|
* Uses a minimal PKZIP (STORE) implementation — the same family as
|
|
* /zip-store.js at repo root. Some fflate/zipSync outputs (UTF-8 flags, extra
|
|
* fields) are rejected or misread by the Java path Qortal uses for WEBSITE
|
|
* validation, yielding false MISSING_INDEX_FILE. This format matches the
|
|
* expectations of java.util zip readers / ZipUtils in core.
|
|
*/
|
|
const crcTable = (function makeTable() {
|
|
const t = new Uint32Array(256);
|
|
for (let i = 0; i < 256; i++) {
|
|
let c = i;
|
|
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
t[i] = c >>> 0;
|
|
}
|
|
return t;
|
|
})();
|
|
|
|
function crc32(buf) {
|
|
let c = 0xffffffff;
|
|
for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
|
return (c ^ 0xffffffff) >>> 0;
|
|
}
|
|
|
|
function toUtf8(v) {
|
|
if (v instanceof Uint8Array) return v;
|
|
if (typeof v === 'string') return new TextEncoder().encode(String(v));
|
|
return new Uint8Array(v);
|
|
}
|
|
|
|
/** Qortal: root index* / default* / home* first, then a stable order. */
|
|
const ROOT_FIRST = [
|
|
'index.html',
|
|
'index.htm',
|
|
'default.html',
|
|
'default.htm',
|
|
'home.html',
|
|
'home.htm',
|
|
'manifest.json',
|
|
];
|
|
|
|
function sortZipKeys(keys) {
|
|
const remaining = new Set(keys);
|
|
const out = [];
|
|
for (const p of ROOT_FIRST) {
|
|
if (remaining.has(p)) {
|
|
out.push(p);
|
|
remaining.delete(p);
|
|
}
|
|
}
|
|
out.push(...Array.from(remaining).sort((a, b) => a.localeCompare(b)));
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, string|ArrayBuffer|Uint8Array>} files
|
|
* @returns {Uint8Array}
|
|
*/
|
|
function buildZipStoreRaw(files) {
|
|
const names = sortZipKeys(Object.keys(files));
|
|
const entries = names.map((name) => {
|
|
const data = toUtf8(files[name]);
|
|
return { name, data };
|
|
});
|
|
|
|
let offset = 0;
|
|
const parts = [];
|
|
const central = [];
|
|
|
|
for (const e of entries) {
|
|
const nameBytes = toUtf8(e.name);
|
|
const nameLen = nameBytes.length;
|
|
if (nameLen > 65535) {
|
|
throw new Error('zip path too long: ' + e.name);
|
|
}
|
|
const data = e.data;
|
|
const size = data.length;
|
|
const crc = crc32(data);
|
|
const sig = 0x04034b50;
|
|
const ver = 20;
|
|
const flags = 0;
|
|
const method = 0;
|
|
const time = 0;
|
|
const date = 0;
|
|
const csize = size;
|
|
const hlen = 30 + nameLen;
|
|
|
|
const local = new DataView(new ArrayBuffer(hlen + size));
|
|
let p = 0;
|
|
local.setUint32(p, sig, true);
|
|
p += 4;
|
|
local.setUint16(p, ver, true);
|
|
p += 2;
|
|
local.setUint16(p, flags, true);
|
|
p += 2;
|
|
local.setUint16(p, method, true);
|
|
p += 2;
|
|
local.setUint16(p, time, true);
|
|
p += 2;
|
|
local.setUint16(p, date, true);
|
|
p += 2;
|
|
local.setUint32(p, crc, true);
|
|
p += 4;
|
|
local.setUint32(p, csize, true);
|
|
p += 4;
|
|
local.setUint32(p, size, true);
|
|
p += 4;
|
|
local.setUint16(p, nameLen, true);
|
|
p += 2;
|
|
local.setUint16(p, 0, true);
|
|
p += 2;
|
|
new Uint8Array(local.buffer).set(nameBytes, p);
|
|
p += nameLen;
|
|
new Uint8Array(local.buffer).set(data, p);
|
|
|
|
parts.push(new Uint8Array(local.buffer));
|
|
central.push({ nameBytes, crc, size, csize, localOffset: offset });
|
|
offset += local.buffer.byteLength;
|
|
}
|
|
|
|
const cdParts = [];
|
|
let cdSize = 0;
|
|
const cdOffset = offset;
|
|
for (let i = 0; i < central.length; i++) {
|
|
const c = central[i];
|
|
const nameLen = c.nameBytes.length;
|
|
const hlen = 46 + nameLen;
|
|
const buf = new DataView(new ArrayBuffer(46));
|
|
let q = 0;
|
|
buf.setUint32(q, 0x02014b50, true);
|
|
q += 4;
|
|
buf.setUint16(q, 20, true);
|
|
q += 2;
|
|
buf.setUint16(q, 20, true);
|
|
q += 2;
|
|
buf.setUint16(q, 0, true);
|
|
q += 2;
|
|
buf.setUint16(q, 0, true);
|
|
q += 2;
|
|
buf.setUint16(q, 0, true);
|
|
q += 2;
|
|
buf.setUint16(q, 0, true);
|
|
q += 2;
|
|
buf.setUint32(q, c.crc, true);
|
|
q += 4;
|
|
buf.setUint32(q, c.csize, true);
|
|
q += 4;
|
|
buf.setUint32(q, c.size, true);
|
|
q += 4;
|
|
buf.setUint16(q, nameLen, true);
|
|
q += 2;
|
|
buf.setUint16(q, 0, true);
|
|
q += 2;
|
|
buf.setUint16(q, 0, true);
|
|
q += 2;
|
|
buf.setUint16(q, 0, true);
|
|
q += 2;
|
|
buf.setUint16(q, 0, true);
|
|
q += 2;
|
|
buf.setUint32(q, 0, true);
|
|
q += 4;
|
|
buf.setUint32(q, c.localOffset, true);
|
|
q += 4;
|
|
const header = new Uint8Array(buf.buffer);
|
|
const merged = new Uint8Array(hlen);
|
|
merged.set(header, 0);
|
|
merged.set(c.nameBytes, 46);
|
|
cdParts.push(merged);
|
|
cdSize += merged.length;
|
|
}
|
|
|
|
const eoc = new DataView(new ArrayBuffer(22));
|
|
eoc.setUint32(0, 0x06054b50, true);
|
|
eoc.setUint16(4, 0, true);
|
|
eoc.setUint16(6, 0, true);
|
|
eoc.setUint16(8, entries.length, true);
|
|
eoc.setUint16(10, entries.length, true);
|
|
eoc.setUint32(12, cdSize, true);
|
|
eoc.setUint32(16, cdOffset, true);
|
|
eoc.setUint16(20, 0, true);
|
|
|
|
const totalLen = offset + cdSize + 22;
|
|
const out = new Uint8Array(totalLen);
|
|
let o = 0;
|
|
for (const part of parts) {
|
|
out.set(part, o);
|
|
o += part.length;
|
|
}
|
|
for (const part of cdParts) {
|
|
out.set(part, o);
|
|
o += part.length;
|
|
}
|
|
out.set(new Uint8Array(eoc.buffer), o);
|
|
return out;
|
|
}
|
|
|
|
export { buildZipStoreRaw };
|