70 lines
2.6 KiB
TypeScript

import { createHash } from "node:crypto";
/**
* Normalisiert eine Domain für Hashing — muss zwischen Server und iOS-Extension
* IDENTISCH sein, sonst stimmen die Hashes nicht überein.
*
* Schritte:
* 1. trim, lowercase
* 2. http:// und https:// entfernen
* 3. Pfad / Query nach erstem `/` abschneiden
* 4. Optional `www.` Prefix entfernen (so dass `www.bet365.com` und `bet365.com`
* den gleichen Hash haben)
*/
export function normalizeDomain(input: string): string {
return input
.trim()
.toLowerCase()
.replace(/^https?:\/\//, "")
.replace(/\/.*$/, "")
.replace(/^www\./, "");
}
/**
* SHA-256 → erste 8 Bytes als big-endian UInt64.
* Wenn salt gesetzt ist, wird `<salt>:<domain>` gehasht (für user-spezifische
* Custom-Domains, damit gleiche Domain bei zwei Usern unterschiedliche
* Hashes ergibt → keine Cross-User-Korrelation möglich).
*/
export function hashDomain(domain: string, salt = ""): bigint {
const normalized = normalizeDomain(domain);
const input = salt ? `${salt}:${normalized}` : normalized;
const digest = createHash("sha256").update(input, "utf8").digest();
return digest.readBigUInt64BE(0);
}
/**
* Hasht eine Liste von Domains, sortiert die Hashes aufsteigend, und gibt
* sie als Binary-Buffer zurück (8 Bytes pro Hash, big-endian).
*
* Format der Binary-Datei für die iOS-Extension:
* ┌────────────────┬────────────────┬─────────────────┐
* │ Hash 0 (8 B) │ Hash 1 (8 B) │ Hash 2 (8 B) │ ...
* └────────────────┴────────────────┴─────────────────┘
* sorted ascending → binary-search möglich, O(log n).
*/
export function buildHashListBinary(domains: string[], salt = ""): Buffer {
const hashes = new BigUint64Array(domains.length);
for (let i = 0; i < domains.length; i++) {
hashes[i] = hashDomain(domains[i], salt);
}
hashes.sort();
// BigUint64Array ist platform-endian — wir brauchen explicit big-endian
// damit Server und iOS-Extension dasselbe Format lesen.
const buf = Buffer.alloc(hashes.length * 8);
for (let i = 0; i < hashes.length; i++) {
buf.writeBigUInt64BE(hashes[i], i * 8);
}
return buf;
}
/**
* ETag aus dem Binary-Content (sha256 der Bytes, hex-encoded).
* Client cached darauf basierend → wenn Server-DB sich nicht ändert,
* spart Bandbreite + Battery.
*/
export function etagFor(buf: Buffer): string {
return `"${createHash("sha256").update(buf).digest("hex").slice(0, 16)}"`;
}